Override rel="canonical" in WordPress

There is a mobile optimized version of this page, view AMP Version.

We have two blogs that we actively use for developer related content, this blog "deano.me" which is my personal blog and WebDesires Blog. The problem is that sometimes posts are relevant for both websites and anyone who knows about Google and their duplicate content policy knows how serious it can be to do this incorrectly.

For those who are unaware the rel="canonical" can be used for duplicate content, for instance lets say we have a Blog A and Blog B, Blog A creates a post and now Blog B wants to share the same post, if you rel="canonical" the Blob B page to Blog A Google will recognise that you are sharing the content from Blog A on Blog B and will attribute all credit to Blog A, more importantly it will not class Blog B's post as duplicate content, and in some degree Blog B can still pick up ranking benefits.

However in WordPress this is not a standard feature, there is no way to change the Canonical easily - however it can be done and here is how to do it.


Code to Override Canonical

To allow overriding the canonical in WordPress you first need to paste the following code snippet at the bottom of your functions.php file, which is located in /wp-content/themes/[your-theme]/:

//Custom Canonical

// A copy of rel_canonical but to allow an override on a custom tag
function rel_canonical_with_custom_tag_override()
global $wp_the_query;

if( !is_singular() ) {
$link = get_canonical_url();
} else if( !$id = $wp_the_query->get_queried_object_id() ) {
$link = get_canonical_url();
} else {
// remove the default WordPress canonical URL function
//remove_action( 'su_head', 'link_rel_canonical_tag' );

// check whether the current post has content in the "canonical_url" custom field
$canonical_url = get_post_meta( $id, 'canonical', true );
if( '' != $canonical_url )
// trailing slash functions copied from http://core.trac.wordpress.org/attachment/ticket/18660/canonical.6.patch
$link = user_trailingslashit( trailingslashit( $canonical_url ) );
$link = get_canonical_url();
remove_action( 'wp_head', 'rel_canonical' );
echo "<link rel='canonical' href='" . esc_url( $link ) . "' />\n";

function get_canonical_url() {
global $wp_query, $wp_rewrite;

//404s and search results don't have canonical URLs
if ($wp_query->is_404 || $wp_query->is_search) return false;

//Are there posts in the current Loop?
$haspost = count($wp_query->posts) > 0;

//Handling special case with '?m=yyyymmddHHMMSS'.
if (get_query_var('m')) {
$m = preg_replace('/[^0-9]/', '', get_query_var('m'));
switch (strlen($m)) {
case 4: // Yearly
$link = get_year_link($m);
case 6: // Monthly
$link = get_month_link(substr($m, 0, 4), substr($m, 4, 2));
case 8: // Daily
$link = get_day_link(substr($m, 0, 4), substr($m, 4, 2),
substr($m, 6, 2));
//Since there is no code for producing canonical archive links for is_time, we will give up and not try to produce a link.
return false;

//Posts and pages
} elseif (($wp_query->is_single || $wp_query->is_page) && $haspost) {
$post = $wp_query->posts[0];
$link = get_permalink($post->ID);
if (is_front_page()) $link = trailingslashit($link);

//Author archives
} elseif ($wp_query->is_author && $haspost) {
$author = get_userdata(get_query_var('author'));
if ($author === false) return false;
$link = get_author_posts_url($author->ID, $author->user_nicename);

//Category archives
} elseif ($wp_query->is_category && $haspost) {
$link = get_category_link(get_query_var('cat'));

//Tag archives
} else if ($wp_query->is_tag && $haspost) {
$tag = get_term_by('slug',get_query_var('tag'),'post_tag');
if (!empty($tag->term_id)) $link = get_tag_link($tag->term_id);

//Day archives
} elseif ($wp_query->is_day && $haspost) {
$link = get_day_link(get_query_var('year'),

//Month archives
} elseif ($wp_query->is_month && $haspost) {
$link = get_month_link(get_query_var('year'),

//Year archives
} elseif ($wp_query->is_year && $haspost) {
$link = get_year_link(get_query_var('year'));

} elseif ($wp_query->is_home) {
if ((get_option('show_on_front') == 'page') && ($pageid = get_option('page_for_posts')))
$link = trailingslashit(get_permalink($pageid));
$link = trailingslashit(get_option('home'));

} else
return false;

//Handle pagination
$page = get_query_var('paged');
if ($page && $page > 1) {
if ($wp_rewrite->using_permalinks()) {
$link = trailingslashit($link) ."page/$page";
$link = user_trailingslashit($link, 'paged');
} else {
$link = esc_url( add_query_arg( 'paged', $page, $link ) );

//Handle protocol change
//if ($scheme = $this->get_setting('canonical_url_scheme', 'http'))
$scheme = 'http';
$link = preg_replace('@^https?://@', "$scheme://", $link);

//Return the canonical URL
return $link;
// replace the default WordPress canonical URL function with your own
add_action( 'su_head', 'rel_canonical_with_custom_tag_override',1 );


How to Override Canonical

When you write any post you wish to Canonical to another page simply add a "custom field" with the name "canonical" and value of the URL you require, the canonical will then be overridden to the value you have inserted. However if you do not add a canonical custom field a default one is generated linking back to the same content as it should do.


Author: Dean Williams

I'm a Web Developer, Graphics Designer and Gamer, this is my personal site which provides PHP programming advice, hints and tips