How To Change The WordPress Post Template Via URL Parameter

| Created: July 27th, 2011
WordPress Development 25 Comments

Have you ever wanted to be able to serve up an alternative version of your posts, perhaps tweaking the layout or even the content of the posts? Not all the time, just in certain circumstances?

I’ve had two cases recently where I’ve needed to do this. My solution: to change the post template when a certain URL parameter is added to the URL.

When this technique is implemented, a user going to:

http://domain.com/postname/

will get the normal post in the normal template. A user going to

http://domain.com/postname/?template=basic

will be presented the post in an alternative layout or even with altered content.

To make what we’re trying to achieve clear, let me first outline my two use cases. After that, I’ll show you how to achieve this by a) serving up a different post template based on a URL parameter and b) creating the custom post template.

Use Case 1: Removing Headers, Footers And Sidebars

For one project I’m working on, I needed to serve up the post content only. Everything else, including the header, footer and the sidebars needed to be stripped out. I needed this ‘post content only’ version to be always available, for every post, but for the original fully styled posts to be available as well.

Here are some screenshots from my local development environment to demonstrate what I want to achieve. The full post lives at http://localhost/wprs-support/mark-webber/ and displays normally:

screenshot showing the normal post

When ?template=basic is appended to the URL (ie http://localhost/wprs-support/mark-webber/?template=basic), the custom post template is triggered and just the content is displayed:

screenshot showing the basic content only version of the post

Why do I want to do this? I’ll be pulling in the content into another page, probably via an Ajax call (or possibly through an iFrame). I don’t need the headers, sidebars etc as they will already be displayed on the page I’m pulling the content into. It’s not possible to only pull in a section of a page in an iFrame or via Ajax, so I need to strip that stuff out.

Okay, it’s possible to get the whole page and then strip out the stuff I don’t want via JavaScript, but that means that it’s downloading the whole page unecessarily, making it slower, etc. It’s much better to be able to download just the content itself without the rest of the presentation layer.

Use Case 2: Changing Content

I’m the lead developer (but not the owner) of the WP Review Site plugin. I recently had a customer asking to get the average rating for a particular post, so they could display it in a banner on another site via an iFrame. There are other ways to achieve this, but the simplest is to use the URL Parameter and post template combination to serve up just the average rating, as follows:

screenshot showing just the rating

The URL used here is the same as the one for the ‘post content only’ version shown above, but I’ve swapped out the custom post template. Rather than just stripping out the header, sidebar and footer, it’s actually serving up different content: the average rating for the post, instead of the post content.

How To Serve Up A Different Template

It’s reasonably easy to use a custom post template when a certain URL parameter is detected.

First we need to let WordPress know about the URL parameter so we can detect it later. We do this by adding the following code to your theme’s functions.php file:

[sourcecode language=”php”]function sjc_add_query_vars($vars) {
return array(‘template’) + $vars;
}
add_filter(‘query_vars’, ‘sjc_add_query_vars’);[/sourcecode]

We use the query_vars filter (line 4) to call a function (line 1) that returns the existing array of query vars with our new one added on the front (line 2). I’ve used ‘template’ as the URL parameter and have continued to use that below, but you can use what ever you want (as long as it’s not one that’s commonly used already).

Note: We could just use $_GET[‘template’] rather than adding it to the query_vars array, but it’s not best practice. Using the query_vars method future proofs us in case we later decide to rewrite the URL structure to make fancy permalinks for this URL parameter, eg: http://domain.com/postname/template/basic/ instead of http://domain.com/postname/?template=basic

Next, we check for the existence of the URL parameter / query_var, by adding the following code to your theme’s functions.php file (below the code given above):

[sourcecode language=”php”]function sjc_template($template) {
global $wp;
if ($wp->query_vars[‘template’]==’basic’) {
return dirname( __FILE__ ) . ‘/single-basic.php’;
}
else {
return $template;
}
}
add_filter(‘single_template’, ‘sjc_template’);[/sourcecode]

We use the single_template filter (line 10) to call a function called sjc_template (line 1). This function checks if the URL parameter is set (line 3) and, if so, returns the path to the single-basic.php template file (line 4). If the URL parameter doesn’t exist, the default template is returned unchanged (line 7).

Once again, I’ve used single-basic.php, but you can call this file whatever you want – as long as you are consistent with the name when creating the template file below.

Disclaimer: The above code will fail if the single-basic.php file(or whatever you’ve called it) doesn’t exist. We should probably use file_exists to check it’s there and error out nicely if not. I’d do that if I was publically releasing a theme or plugin or doing client work, but haven’t bothered in this case.

There is an increasing trend to put code such as this into a ‘custom functionality’ plugin rather than functions.php, so that it is still available if you switch themes. That’s a great idea in general, but not particularly useful in this case, because the code is calling a custom template in your theme directory. Switch themes and this will break anyway, unless you recreate the template in the new theme.

How To Create The Custom Post Template

Once we’ve told WordPress to use a custom post template instead of single.php, we need to create that custom post template.

In your theme folder you need to create a file called single-basic.php (you can call this whatever you want, as long as it matches the file name requested in the previous step).

What’s in this file? Whatever you want to tweak the post. For my ‘post content only’ format shown above, I used the following:

[sourcecode language=”php”]
<div id="content">
<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>
<h2 class="item"><span class="fn"><?php the_title(); ?></span></h2>
<?php the_content(); ?>
<?php endwhile; else: ?>
<?php endif; ?>
</div>
[/sourcecode]

Note: this just creates part of the page. It doesn’t have html, head or body tags. That’s probably no good if you want people to actually visit this page in the browser, but in my case it’s primarily for me to pull the content in to be displayed in a page that already has a html, head and body tags. It’s better for me to have it ready to go.

If you need to have a full HTML page, simply add the tags to single-basic.php. You can probably get the basic code from your header.php file, leaving out the extra lines that aren’t necessary. You could even link to a custom CSS file if you just want to make style changes, although there is a better way to do that (without using custom post templates).

This technique could cause duplicate content in the eyes of the search engines. It may be best to add <meta name="robots" content="noindex"> to the head of the page (if you have that section) or to exclude pages with this paramters in robots.txt.

For my ratings only example from above, my single-basic.php file contains the following:

[sourcecode language=”php”]
<?php
if (function_exists(‘get_average_rating’)) {
global $post;
echo round_to_half(get_average_rating($post->ID));
} ?>
[/sourcecode]

I don’t need the post content. I just need the average rating, which I’m getting through the WP Review Site API. This will return a plain number, not wrapped in any HTML. Once again, if a full HTML page is required, you need to add the appropriate tags to the template file.

Critically, WordPress is still serving up the same post, even if we’re not choosing to display it. The post ID and everything else associated with the post is still present and available to us.

Final Thoughts

This technique has served me well on a number of occasions. It provides the flexibility to serve up altered content / layout for your posts, through the power of custom post templates.

Have you ever done something similar? If so, how did you do it?

25 responses on “How To Change The WordPress Post Template Via URL Parameter

  1. dains

    Hi, I just wanted to say that I commissioned nearly identical functionality from a developer for exactly the same reason! My implementation uses pages, so it was easier as pages already have a fully supported template system, but we had to ensure any page viewed in a popin had the correct template, and that meant a dynamic switching system for the page templates.

    I wanted to add that this also addresses a functionality issue when using popins to display WP content, as if the header file loads again, WP can pitch a fit over duplicate headers and possibly even whitescreen the page on you depending on what plugins are doing in wp_header. So this little trick is more important than it may seem 🙂

  2. plainflavour

    Hi,
    I’m trying to achieve the same thing, but for pages rather than posts.

    I’m having problems though. Firstly – I get an error in my functions.php with this line:

    if ($wp-&gt;query_vars[‘template’]==’basic’)

    It says there is an ampersand in the wrong place.

    I tried > instead but that also didn’t work.

    Any ideas?

    thanks

  3. Toine

    Hi, thanks for this great post, I was looking for this!
    So if I understand correctly: you could add this parameter to the post permalink in your page-template to get a different single-post template something like this:

    href="/?template=basic"

    If I wanted to rewrite the url with parameter to a different domain, would my .htaccess file look something like this?

    RewriteCond %{HTTP_HOST} =www.originaldomain.com [NC]
    RewriteCond %{QUERY_STRING} (template=basic)
    RewriteRule ^$ http://www.newdomain.com/ [R=301,L]

    Thanks for your help!
    Cheers

  4. Toine

    It removed ‘the_permalink()’ in the href by the way…

    One other thing; how could this trick work if I allready have multiple single template files for multiple custom post types? Then the following line wouldn’t work:

    return dirname( __FILE__ ) . '/single-basic.php';

    Somehow I need to insert the ‘basic’ variable in the filename rather then renaming the whole filename. Any ideas?
    Thanks!

  5. Toine

    Sorry to bug you again, found a solution that I wanted to share with you!


    function sjc_template($template) {
    global $wp;
    if ($wp->query_vars['template']=='basic') {
    global $post;
    $posttype = get_post_type($post->ID);
    return dirname( __FILE__ ) . '/single-'. $posttype .'-basic.php';
    }
    else {
    return $template;
    }
    }
    add_filter('single_template', 'sjc_template');

    Works for me!

  6. Scott

    Hi there,

    I get an error when i paste the code in my functions file:

    function sjc_template($template) {
    global $wp;
    if ($wp-&gt;query_vars[‘template’]==’basic’) {
    return dirname( __FILE__ ) . ‘/single-basic.php’;
    }
    else {
    return $template;
    }
    }
    add_filter(‘single_template’, ‘sjc_template’);

    Any ideas?

    Thanks,

  7. Scott

    I have the same issue as plainflour regarding my previous comment.

    if ($wp->query_vars[‘template’]==’basic’)

  8. Stephen Cronin

    Hi Scott,

    I think there is something wrong with the code and that is should be:

    if ($wp->query_vars['template']=='basic')

    For some reason the > has been converted into special characters (and also you seem to have curly quotes around basic).

    Anyway, please try that and let me know if it doesn’t help.

    Cheers,
    Stephen

  9. Scott

    Hi Stephen,

    Thanks for clarifying that for me. As you can see even in the last comment, it converted the > (accent sign) into > – just need to make sure this is changed to the >

    Thanks very much for getting back to me mate. I’ll can now move on and see if it works.

  10. Kris

    Thanks for this tutorial. How would you implement this with a custom post type?

    where:

    mysite.com/custom/mypost

  11. Kris

    I noticed the original code was lacking a bracket. It should be?

    function sjc_template($template) {
    global $wp;
    if ($wp->query_vars[‘template’]==’basic’) {
    return dirname( __FILE__ ) . ‘/single-basic.php’;
    }
    else {
    return $template;
    }
    }
    add_filter(‘single_template’, ‘sjc_template’);

  12. Paul123456

    Really nice explanation and a technique I will use to create embeddable iframes for my site content.

    My only concern is how this impacts caching and how to encourage caching of the alternative template version of pages.

  13. Kurt

    How would I create a link that passes the basic parameter into the exisiting url so that the user could opt to view the basic template display.

  14. Kurt

    This is how I approached it. Is this the best way?
    <a href="
    ID );
    if ( get_option(‘permalink_structure’) ) {
    echo ‘?template=basic’;
    } else {
    echo ‘&template=basic’;
    }
    ?>
    “>Printer Friendly Page

  15. webdesign_hi

    Hi Stephen, good tutorial, helped me and worked on the first go!
    Thank you very much! Karin

  16. Eric

    I have tried all the suggestions above, and still get:

    Notice: Undefined index: template in url/functions.php on line 34

    My complete functions.php:
    is_main_query()){
    $query->is_search = true;
    $query->is_home = false;
    }
    return $query;
    }
    add_filter(‘pre_get_posts’,’SearchFilter’);
    function custom_excerpt_length( $length ) {
    return 20;
    }
    add_filter( ‘excerpt_length’, ‘custom_excerpt_length’, 999 );
    //Gets post cat slug and looks for single-[cat slug].php and applies it
    add_filter(‘single_template’, create_function(
    ‘$the_template’,
    ‘foreach( (array) get_the_category() as $cat ) {
    if ( file_exists(TEMPLATEPATH . “/single-{$cat->slug}.php”) )
    return TEMPLATEPATH . “/single-{$cat->slug}.php”; }
    return $the_template;’ )
    );
    function sjc_add_query_vars($vars) {
    return array(‘template’) + $vars;
    }
    add_filter(‘query_vars’, ‘sjc_add_query_vars’);
    function sjc_template($template) {
    global $wp;
    if ($wp->query_vars[‘template’]==’order’) {
    return dirname( __FILE__ ) . ‘/single-order.php’;
    }
    else {
    return $template;
    }
    }
    add_filter(‘single_template’, ‘sjc_template’);
    ?>

    Any suggestion?

  17. Carlos

    Thanks so much Stephen, great tidy way to tackle a common WP request.

    I had the same problem as others here: I wanted to implement this at a page level (not posts). Ended up being incredibly easy to fix, but posting the solution here just in case someone is still trying to find an answer. Simply replace the ‘single_template’ bit on the last line of code (add_filter) with ‘page_template’.

    1. Stephen Cronin Post author

      Hi Ulziibat,

      Sorry I missed your comment.

      This tutorial does actually use the function that you linked to, but it also does more – it’s not just filtering the template for *every* page load of a particular page, it’s changing either the content OR the template *sometimes* (ie you add a URL parameter when you want something different).

      That said, I wrote this a long time ago and wouldn’t be surprised if there was a better of doing this now.

      For the content parts, I’d probably use the WP REST API now. Use case 2 above would require custom code and could be created right now as the WP REST API infrastructure is in core already. You could create custom code for use case 1 as well, but the REST API content end points will be added in WordPress 4.7, so you can just use that when it’s added.

  18. James

    Awesome post!!
    How would I activate this on custom post types? It works perfectly on general blog posts, but say I have a CPT called “services” how would I activate the template there?

  19. Alex

    Is this code example still functional? Or am I doing something wrong? I added the following to the bottom of my functions.php page and added a template file single-iframe.php to my theme folder:
    `function hfcw_add_query_vars($vars) {
    return array(‘template’) + $vars;
    }
    add_filter(‘query_vars’, ‘hfcw_add_query_vars’);

    function hfcw_template($template) {
    global $wp;
    if ($wp->query_vars[‘template’]==’iframe’) {
    return dirname( __FILE__ ) . ‘/single-iframe.php’;
    }
    else {
    return $template;
    }
    }
    add_filter(‘single_template’, ‘hfcw_template’);`

    1. Alex

      And I forgot to mention that when I go to a page and add “?template=iframe” to the end, nothing changes. The page loads like it normally does, it does not use my custom template.