Add canonical URL tag to WordPress without a plugin

Search engines can index the same content under multiple different URLs, and when that happens they have to decide which version is the authoritative one to rank. On a WordPress site this situation is more common than most developers realize. A single blog post may be reachable at its direct permalink, inside a category archive, inside a tag archive, through a date-based archive page, and through the author archive — all rendering an identical or near-identical page. Pagination layers another dimension on top: /page/2/ of a category listing shares the same base content as page one. Without a canonical link tag pointing to the preferred URL, search engines may split ranking signals across all of these addresses instead of consolidating them onto the one you actually care about. This dilution of link equity and relevance signals is one of the most common technical SEO problems on WordPress sites and is entirely avoidable. A canonical tag placed inside the <head> element tells crawlers which URL is the preferred, definitive version and instructs them to attribute any inbound link value to that address. Full-featured SEO plugins like Yoast or RankMath handle this automatically, but if your site runs a lighter setup without a heavyweight SEO plugin, you need to output the tag yourself. WordPress fires the wp_head action inside the <head> of every front-end page, making it the correct attachment point for this output. The get_permalink() function returns the correct canonical URL for any singular post or page, already normalized to match your permalink structure settings. A priority of 1 on the action hook ensures the tag appears early in the head before any conflicting output. The implementation below covers singular content, which represents the majority of indexable URLs on most WordPress blogs and business sites.

Problem: WordPress does not output a canonical link tag on posts and pages without an SEO plugin.

Solution: Add the following code to your functions.php file:

<?php
add_action( 'wp_head', 'ha_output_canonical_url', 1 );

function ha_output_canonical_url() {
    if ( is_singular() ) {
        $canonical_url = get_permalink();
        echo '<link rel="canonical" href="' . esc_url( $canonical_url ) . '" />' . "
";
    }
}

NOTE: If you already use Yoast SEO or RankMath, do not add this snippet — those plugins already output canonical tags and adding a duplicate creates a validation error that confuses crawlers. The is_singular() check covers posts, pages, and custom post type single views. To extend coverage to category and tag archive pages, add an elseif ( is_tax() || is_category() || is_tag() ) branch using get_term_link( get_queried_object() ) as the canonical URL.