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

The main purpose of a canonical tag is to indicate the preferred version of a page when identical or similar content appears under different URLs. Without it, search engines might misinterpret duplicate variations as separate pages, which can downgrade search performance.

Many people use SEO plugins like Yoast SEO or Rank Math to manage canonical URLs, and those work fine. But if you prefer keeping your WordPress site lean and free from too many plugins, there is a much simpler way.

In this article, we walk through how you can add your own custom canonical tag logic right, without relying on any plugins. Before trying the code, though, make sure you understand what canonicalization is and why it is important for search engines.

What Are Canonical URLs and Tags?

The canonical URL is usually added as a <link rel="canonical" /> tag in the <head> part of the HTML code. While this tag is invisible to visitors, it allows search engine crawlers to determine which URL should prioritize for indexing.

How to find canonical URL?

WordPress, by default, adds a meta tag with the canonical URL to the HTML output, even if you do not have any plugins installed. However, the rel_canonical() function works only for single posts and pages. This means that it does not apply to other types of content on your site.

How to Add Canonical Tags Without Any Plugin?

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)

Limitations and Customization Tips

This code is purposely simple and meant to act as a lightweight alternative to using a separate plugin. Feel free to adjust and extend it for your specific needs.

Due to its simplicity, there is no guarantee it will work seamlessly with all third-party plugins. Make sure to test it thoroughly on your site, especially if you are using custom post types or non-standard URL structures.

Where to Place the Code?

The main advantage of this solution is that you do not need to add another plugin to your site. Though you could use a plugin like Code Snippets to add the code, it is more convenient to insert the code directly into the functions.php file of your child theme.

The Code

<?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 '';
}

Last updated by Maciej Bis on: April 30, 2025.


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 *