Using canonical tags together with redirects is a simple but important step for technical SEO. WordPress can make your content available under different URLs. If this is overlooked, search engines may flag it as duplicate content which might hurt your SERP ranking.
Even if you declare a canonical URL, they might ignore your preferred URL. Still, it is considered good practice and worth implementing. WordPress handles this on its own to some extent, though it is limited.
Many turn to SEO plugins for finer control. If you prefer to keep your site lightweight and avoid additional plugins, implementing your own custom canonical tag function is a most reliable solution.
Understanding Canonical Tags
To decide which version of a URL should be listed in search results, Google considers multiple signals, including redirects, meta tags, and sitemaps URLs.
Each of these signals tells which page variant should be prioritized, helping to prevent duplicate content issue.
The canonical URL plays a key role here although is invisible to human visitors. By default, you should find it as a separate HTML tag inside HTML output when checking the <head> section.
<link rel="canonical" href="https://example.com/preferred-page/" />
Why Are Canonical Tags Important?
Search engines use them to determine the preferred URL when multiple variants exist. If this tag is missing, crawlers may incorrectly index a different URL, leading to the non-preferred version appearing in search results.
The biggest problem here in this case is that the ranking signals are being diluted across multiple URLs, which prevents the original URL from consolidating authority and maximizing its performance.
The implications continue further, and this can also lead to crawl budget waste, which is especially relevant for websites with a large number of pages, as this can delay the discovery and indexing of new or updated high-priority content.
How WordPress Handles Canonical URLs by Default?
WordPress automatically inserts canonical URL meta tags into your HTML output, even if you do not use plugins. This is not the only built-in mechanism to manage canonicalization.
WordPress also supports canonical redirect, which take users and search engines directly to the preferred page.
Limitations of the Default Solution
For most standard websites, this solution should be enough. The biggest problem here is that rel_canonical() function works only for single posts and pages.
It does not cover other content types, including taxonomies or custom post types, which it does not serve your entire website. There are two options to implement canonical tags.
The majority of SEO plugins handle canonical tag insertion automatically, yet it is also possible to implement them programatically. A plugin simplifies the process, requiring no technical input, whereas a snippet gives you full control over how tags are added.
Adding Canonical Tags With Code Snippet
Why and When to Use a Custom Solution?
Sometimes, plugins can result in conflicting or duplicated canonical tags, particularly with custom post types, archives, or complex layouts. This code snippet gives you full control, letting you adjust the canonical logic exactly how you need.
This snippet below replaces the built-in function and adds the canonical tag for all post types, taxonomies, and archives:
- Front page
- Blog index page
- Single posts, pages, and custom post types
- Paginated content
- Category and taxonomy archives
- Author archives
- Date-based archives (year/month/day)
The Snippet
This is a standard solution designed to work for most WordPress websites and you only need a basic skills to implement it. Keep in mind, that it may not work with every plugin you might have installed.
<?php
/**
* Remove the built-in canonical tag to make sure that is not duplicated
* with the new custom code
*/
remove_action( 'wp_head', 'rel_canonical' );
/**
* Adds a canonical <link> tag to the <head> for various types of pages.
*
* Supports single posts/pages, taxonomies, custom post type archives,
* author archives, date archives, and paginated archives.
*
* @return void
*/
function add_canonical_link() {
	$canonical_url = '';
	// A. Canonical URL for the front page.
	if ( is_front_page() ) {
		$canonical_url = home_url( '/' );
	} // B. Canonical URL for the blog posts page.
	elseif ( is_home() ) {
		$page_id = get_option( 'page_for_posts' );
		$canonical_url = $page_id ? get_permalink( $page_id ) : get_home_url();
	} // C. Canonical URL for single posts/pages/CPT items
	else if ( is_singular() ) {
		$canonical_url = wp_get_canonical_url();
	} // D. Canonical URL for paginated archives
	elseif ( is_paged() ) {
		$canonical_url = get_pagenum_link( get_query_var( 'paged' ) );
	} // E. Canonical URL for taxonomy or post type archive.
	elseif ( is_category() || is_tax() || is_post_type_archive() ) {
		$canonical_url = get_archive_link( get_queried_object() );
	} // F. Canonical URL for author archive
	elseif ( is_author() ) {
		$author = get_queried_object();
		$canonical_url = get_author_posts_url( $author->ID );
	} // G. Canonical URL for date archive
	elseif ( is_date() ) {
		$canonical_url = get_date_archive_link();
	}
	if ( ! empty( $canonical_url ) ) {
		echo '<link rel="canonical" href="' . esc_url( $canonical_url ) . '" />' . PHP_EOL;
	}
}
add_action( 'wp_head', 'add_canonical_link' );
/**
* Returns the archive link for a taxonomy term or a post type archive.
*
* @param object $archive The queried object (WP_Term or WP_Post_Type).
*
* @return string Canonical URL for the archive.
*/
function get_archive_link( $archive ) {
	if ( is_category() || is_tax() ) {
		return get_term_link( $archive );
	}
	if ( is_post_type_archive() ) {
		return get_post_type_archive_link( $archive->name );
	}
	return '';
}
/**
* Returns the canonical URL for a date archive (year, month, or day).
*
* @return string Canonical URL for the date archive.
*/
function get_date_archive_link() {
	$year = (int) get_query_var( 'year' );
	$month = (int) get_query_var( 'monthnum' );
	$day = (int) get_query_var( 'day' );
	if ( $year && $month && $day ) {
		return get_day_link( $year, $month, $day );
	}
	if ( $year && $month ) {
		return get_month_link( $year, $month );
	}
	if ( $year ) {
		return get_year_link( $year );
	}
	return '';
}

Leave a Reply