How to Set Custom Canonical Tags in WordPress Without a Plugin?

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/" />

How to find canonical URL?

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 adds canonical meta tags on its own, even if you do not have any extra SEO plugin installed. The built-in rel_canonical() function prints the tag in the ' <head> ' section of every single post and page.

On top of that, WordPress also uses canonical redirect to send users and search engine crawlers directly to the preferred canonical URL.

Limitations of the rel_canonical() Function

The catch is that rel_canonical() function works only for single posts and pages. Archive pages, such as categories, tags, custom taxonomies and authors are generated without a canonical tag.

In 2011, Nathan Rice suggested adding support for archive pages to this function. Although his proposal was discussed, it has never been implemented, and the Trac ticket is still open.

There are two ways to fix this. You can install an additional SEO plugin, or you can add the meta tags to archive pages with a short code snippet.

How to Add Canonical Tag in WordPress?

Why and When to Use a Custom Snippet?

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:

  1. Front page
  2. Blog index page
  3. Single posts, pages, and custom post types
  4. Paginated content
  5. Category and taxonomy archives
  6. Author archives
  7. 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.

While you could use a plugin like Code Snippets to add it, the simplest and most straightforward way is to place the code directly into the functions.php file file of your child theme.
<?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 archive pages
	elseif ( is_category() || is_tax() || is_post_type_archive() || is_author() || is_date() ) {
		$canonical_url = get_archive_link( get_queried_object() );
	}
	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 post type archive,
* including pagination when applicable.
*
* @param object $archive The queried object.
*
* @return string
*/
function get_archive_link( $archive ) {
	$link = '';
	if ( is_category() || is_tax() ) {
		$link = get_term_link( $archive );
	} elseif ( is_post_type_archive() ) {
		$link = get_post_type_archive_link( $archive->name );
	} elseif ( is_author() ) {
		$link = get_author_posts_url( $archive->ID );
	} elseif ( is_date() ) {
		$link = get_date_archive_link();
	}
	if ( empty( $link ) ) {
		return '';
	}
	$paged = max( 1, get_query_var( 'paged' ) );
	if ( $paged > 1 ) {
		$link = get_pagenum_link( $paged );
	}
	return $link;
}
/**
* 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 = absint( get_query_var( 'year' ) );
	$month = absint( get_query_var( 'monthnum' ) );
	$day = absint( 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 '';
}

Last updated by Maciej Bis on: June 2, 2026.


Maciej BisFounder of Permalink Manager & WordPress Developer

The developer behind Permalink Manager, a plugin for managing permalinks, has been working with WordPress, creating custom plugins and themes, for more than a decade.

Leave a Reply

Your email address will not be published. Required fields are marked *