Override rel=canonical in Wordpress

WARNING: This article features ANCIENT code! I'm keeping it online because it's interesting to see what I was thinking 10+ years ago. But you DEFINITELY should not be using this code. Anything you're reading about on this page has changed significantly since this was written.

A recent project required cross-posting content between two sites. To avoid duplicate content issues, we're using rel="canonical" to indicate which post is the original and which is the duplicate. While not 100% ideal, evidently cross-domain rel canonical is an okay thing to do.

Oddly, right now, Wordpress does not seems to have a filter to modify the canonical link, but it does have an action you can replace with your own. Here's what you do:

<?php
// in an init hook
// unregister the default action
// and register our own
do_action('init', 'my_init');
function my_init() {
  remove_action( 'wp_head', 'rel_canonical' );
  add_action( 'wp_head', 'my_rel_canonical' );
}

// this is slightly modified from the original
// rel_canonical function in /wp-includes/link-template.php
// 
// assumes we have a custom post_meta property
// called 'my_canonical'
function my_rel_canonical() {
  // original code
  if ( !is_singular() )
    return;
  global $wp_the_query;
  if ( !$id = $wp_the_query->get_queried_object_id() )
    return;

  // new code - if there is a meta property defined
  // use that as the canonical url
  $canonical = get_post_meta( $id, 'my_canonical', TRUE );
  if( $canonical ) {
    echo "<link rel='canonical' href='$canonical' />\n";
    return;
  }

  // original code
  $link = get_permalink( $id );
  if ( $page = get_query_var('cpage') )
    $link = get_comments_pagenum_link( $page );
  echo "<link rel='canonical' href='$link' />\n";
}

And.. that's it. Pretty easy!

As mentioned above, this assumes you have a custom meta property called 'my_canonical' to store the url. If you had a really complicated use case, it might be even cleaner to override this action to implement a custom filter, but this is pretty clean and was good enough for my purpose.