WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I have two pages, one called cities and the other called states. They use the add_query_vars function to grab the city and or state from a database. The pages load fine. For example when someone goes to sitename.com/roller-derbies/arizona/florence/ and the events in that city display. But when I view the page source and look at the canonical link inserted in wp-head it says sitename.com/cities or sitename.com/states after the page name. This creates a problem where none of my pages are being indexed by google as they all point to canonical link for cities or states. What can I do so the canonical link matches the url?

Below is the code I am using.

add_filter('query_vars', 'add_query_vars');
function add_query_vars($aVars) {
    $aVars[] .= 'var_state';
    $aVars[] .= 'var_city';
    return $aVars;

add_filter('rewrite_rules_array', 'add_rewrite_rules');
function add_rewrite_rules($aRules) {
    $aNewRules = array(
    'roller-derbies/([^/]+)/?$' => 'index.php?pagename=states&var_state=$matches[1]', 
    'roller-derbies/([^/]+)/([^/]+)/?$' => 'index.php?pagename=cities&var_state=$matches[1]&var_city=$matches[2]', 
    $aRules = $aNewRules + $aRules;
    return $aRules;
share|improve this question

WordPress thinks you're on those pages because of the pagename query var in your rewrite rules.

Your journey is going to start in wp-includes/default-filters.php, which every WP developer should known and love: it's where the WordPress care uses its own plugin API to hook in and change stuff.

Of interest to your question:

add_action( 'wp_head',             'rel_canonical'                          );

If you take a look at the rel canonical function in wp-includes/link-template.php you find this:

 * Output rel=canonical for singular queries.
 * @package WordPress
 * @since 2.9.0
function rel_canonical() {
    if ( !is_singular() )

    global $wp_the_query;
    if ( !$id = $wp_the_query->get_queried_object_id() )

    $link = get_permalink( $id );

    if ( $page = get_query_var('cpage') )
        $link = get_comments_pagenum_link( $page );

    echo "<link rel='canonical' href='$link' />\n";

Unfortunately no filters to modify the link. So what you'll need to do is hook in someplace late, but before wp_head fires, check for your query vars, unhook the rel_canonical function and roll your own.

First step, clean up your rewrites a bit:

add_action('init', 'wpse64535_add_rewrite_rules');
function wpse64535_add_rewrite_rules()


add_filter('query_vars', 'wpse64535_add_query_vars');
function wpse64535_add_query_vars($vars)
    $vars[] = 'var_state';
    $vars[] = 'var_city';
    return $vars;

Second part, hook into template_redirect and check for the custom query variables. If you find either, remove the default rel_canonical function and use your own.

add_action('template_redirect', 'wpse64535_maybe_fix');
function wpse64535_maybe_fix()
    if(get_query_var('var_state') || get_query_var('var_city'))
        remove_action('wp_head', 'rel_canonical');
        add_action('wp_head', 'wpse64535_fix_canonical');

function wpse64535_fix_canonical()
    $link = home_url('roller-derbies/');

    // might want to validate these before just using them?
    if($state = get_query_var('var_state'))
        $link .= "{$state}/";

    if($city = get_query_var('var_city'))
        $link .= "{$city}/";

    echo '<link rel="canonical" href="' . esc_url($link) . '" />';

All of the above as a plugin.

share|improve this answer
Thank you very much. This sounds promising. I will try it when I get back home. I thought perhaps I was going to need to use some regular expressions in the .htaccess file. I prefer your method. – Kenny Scott Sep 8 '12 at 14:32
Thank you very much. It worked perfectly. – Kenny Scott Sep 8 '12 at 19:15
Feel free to mark as accepted :) – chrisguitarguy Sep 8 '12 at 20:14

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

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