7

Warning: fairly pedantic request ahead.

I have a design that makes use of posts (I.e., the blog) and a custom post type called "howto". The site front page ("/") has a template that displays the latest blog posts and latest howtos. When the user navigates to the blog section ("/blog"), they see a different template than when they navigate to the howto section ("/howto").

I know the obvious way of doing this is to create two pages ("Blog" and "Home"), then use the drop-downs in Settings->Reading Options to explicitly set those. I can then associate specific page templates to those posts.

But darned if that isn't really messy -- I'll have two useless empty pages cluttering any admin-side list of pages with actual content in them. You'd think there'd be some way to use template hinting to prevent this from being necessary:

  • /howto/ -> archive-howto.php
  • /blog/ -> archive.php

So really, I guess the question really is as follows:

How does one set a custom path for posts without creating a new page?

8
  • How exactly did you make front page display two post types? This is hard to think through without replicating your setup and experimenting with it...
    – Rarst
    Commented Oct 12, 2012 at 13:04
  • @Rarst -- It's just how WordPress displays posts on the front page by default, plus an extra sidebar with a new WP_Query that gives a listing of the howtos. Probably should have been clearer about that...
    – aendra
    Commented Oct 12, 2012 at 16:00
  • Does it paginate for either or is static? Do you want it to?
    – Rarst
    Commented Oct 12, 2012 at 16:03
  • I'll be adding a script that does infinite scroll (Not sure how I'll do that; I know a plugin that works off of pagination, so I might use that).
    – aendra
    Commented Oct 12, 2012 at 16:10
  • 1
    Relevant Trac ticket: core.trac.wordpress.org/ticket/21665 Additionally, the "posts page" will use the template home.php first, if it even uses archive.php at all. Commented Oct 13, 2012 at 18:32

3 Answers 3

11

I don't have time to explain this in detail (I shall upon return) but in the meantime this should work for you,

Answer updated with explanation as promised.
WP Rewrite rules are like voodoo, I'm sure there's more than one way to go about this, but here's mine.

Problem:

Just to clarify your question for others who may stumble upon this thread, what you want to do is create a page without having to physically create an empty-placeholder-page within the administration dashboard found under: Page -> Add New.

Essentially, you want to create a Fake Page and have that page use any template you specify.

Solution:

First we setup our rewrite rules,

add_action('init', 'fake_page_rewrite');

function fake_page_rewrite(){

    global $wp_rewrite;

    //set up our query variable %fake_page% which equates to index.php?fake_page= 
    add_rewrite_tag( '%fake_page%', '([^&]+)'); 

    //add rewrite rule that matches /blog/page/2, /blog/page/3, /blog/page/4, etc..
    add_rewrite_rule('^blog/page/?([0-9])?','index.php?fake_page=blog&paged=$matches[1]','top');  

    //add rewrite rule that matches /blog
    add_rewrite_rule('^blog/?','index.php?fake_page=blog','top');

    //add endpoint, in this case 'blog' to satisfy our rewrite rule /blog, /blog/page/ etc..
    add_rewrite_endpoint( 'blog', EP_PERMALINK | EP_PAGES );

    //flush rules to get this to work properly
    $wp_rewrite->flush_rules();

}

Within the add_rewrite_tag we specify our query variable in the form of %fake_page%, which by the way you can specify whatever you want or appropriate for your needs. My example fake_page is only symbolic to illustrate the mechanics of this answer.

How the query variable works in this instance is by matching a request for,

http://www.example.com/blog

...which is then internally mapped to,

http://www.example.com/index.php?fake_page=blog

The latter being what you would see when running the default permalink structure.

In a similar fashion requests for,

http://www.example.com/blog/page/2
http://www.example.com/blog/page/3
http://www.example.com/blog/page/4
etc...

...would each map to their equivalents,

http://www.example.com/index.php?fake_page=blog&paged=2
http://www.example.com/index.php?fake_page=blog&paged=3
http://www.example.com/index.php?fake_page=blog&paged=4
etc...

In the example snippet above you will notice that we have the rewrite rule which firsts matches the pagination /blog/page/{page_number}, above our second rule which matches the base for our fake page of /blog.

This is necessary so that the base rule doesn't return a match on the first occurrence of our endpoint which is defined as blog before having a chance to evaluate the rest of the requested URL to ensure that the user hadn't in fact requested a paged result. Basically, reverse those rules and it doesn't work.

As mentioned before, rewrite rules are like voodoo to me so there's probably another way to go about the order in which you specify your rules which is possibly related to using the function add_permastruct . If anyone has an alternative, then chime in!

The next function which hooks onto template_redirect checks for the existence of our fake_page within the query variables, if its matched/exists within the array we then request the inclusion of our desired template file to handle the presentation of data.

add_action('template_redirect', 'fake_page_redirect');

    function fake_page_redirect(){

        global $wp;

        //retrieve the query vars and store as variable $template 
        $template = $wp->query_vars;

        //pass the $template variable into the conditional statement and
        //check if the key 'fake_page' is one of the query_vars held in the $template array
        //and that 'blog' is equal to the value of the key which is set
        if ( array_key_exists( 'fake_page', $template ) && 'blog' == $template['fake_page'] ) {

            //if the key 'fake_page' exists and 'blog' matches the value of that key
            //then return the template specified below to handle presentation
            include( get_template_directory().'/your-template-name-here.php' );

            exit;

        }
    }

PS. I have tested this and it works under my conditions, though I'm not sure if any other quirks may popup with rewrite rules and end point masks the deeper you go, therefore you should thoroughly test all pagination and permalinks and make sure that they resolve to their intended paths correctly . If not, we can address those issues as they arise.

3
  • wordpress.stackexchange.com/a/37649/3898 looks like a slightly better version of this IMO.
    – Ian Dunn
    Commented Dec 6, 2019 at 17:29
  • @IanDunn the only thing(s) I'd do differently above is use template_include instead of template_redirect to do the include/require and possibly make use of get_query_var seeings as that is the purpose of using add_rewrite_tag. That would be better practice. Outside of this method, there are other ways to do this if you want to circumvent the request earlier in the request-workflow, which involve hooking into parse_request and request filters found in class WP {} / wp-includes/class-wp.php.
    – Adam
    Commented Dec 6, 2019 at 17:57
  • In the code below, the template is shown when the user enters any address that contains "blog", which is incorrect. To correct this behavior and only show the template when the endpoint name is exactly 'blog' (and not, for example, 'blogabcd' or 'blog1234'), you can modify line 5 as follows: add_rewrite_rule('^blog/?$','index.php?fake_page=blog','top'); This change will ensure that only the endpoint 'blog' triggers the template display, without any additional characters before or after it. Commented Mar 24, 2023 at 13:07
0

Make two archive pages.

archive.php for your blog posts, and archive-howto.php for your howtos.

http://codex.wordpress.org/images/1/18/Template_Hierarchy.png

Making fake pages and futzing with the rewrites is way overkill and somewhat dangerous.

Unless of course I've completely missed the point.

-1

Maybe you are looking for this:

<?php

define ('HOWTO_SLUG', 'my_howtos');
define ('HOWTO_REWRITE_SLUG', 'howtos');

register_post_type (HOWTO_SLUG,
    array
    (
        'labels' => array
        (
            'name' => 'Howtos'
            , 'singular_name' => 'Howto'
        )
        , 'public' => true
        , 'has_archive' => true
        , 'rewrite' => array ('slug' => HOWTO_REWRITE_SLUG)
    )
);

These posts will appear in the WP admin with post_type=my_howtos, while on the public facing side the URL-s will appear as /howtos/, and the file archive-my_howtos.php in the theme will be used to display the page.

The HOWTO_SLUG and HOWTO_REWRITE_SLUG can't be the same, or it will break.

3
  • That's not at all what I'm asking for. I'm wanting to set a slug for posts, not the howtos. I already have the slug set for howtos.
    – aendra
    Commented Oct 10, 2012 at 13:12
  • Maybe you could create a custom post type for the posts
    – gregn3
    Commented Oct 10, 2012 at 13:15
  • Why would I do that? I'm wanting this so I can clean up the interface, not so that I can have an entirely unused content type that WordPress uses by default...
    – aendra
    Commented Oct 10, 2012 at 13:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.