The hreflang attribute on <link rel="alternate"> tags in the HTML <head> signals to Google and Bing which language and regional version of a page corresponds to each other, preventing duplicate content penalties when the same content exists in multiple languages, and ensuring that users searching in French see the French URL in search results rather than the English one. The hreflang value is a language code (ISO 639-1 two-letter code like en, fr, de) optionally combined with a region code (ISO 3166-1 alpha-2 two-letter country code like US, GB, CA): en-US targets English speakers in the United States, en-GB targets English speakers in the UK, fr targets French speakers globally regardless of country. The x-default value designates the fallback page shown to users whose language does not match any specific hreflang — typically the English version or a language-selection landing page. Three implementation requirements must all be met for hreflang to work correctly: (1) every language version of a page must link TO all other language versions AND back to itself; (2) all URLs in hreflang tags must be absolute, not relative; (3) the hreflang tags on the linked pages must be reciprocal — if the English page points to the French page, the French page must point back to the English page with the same set. Hreflang can also be implemented via the XML sitemap (each <url> entry includes <xhtml:link rel="alternate" hreflang="..."> elements) or via HTTP headers (Link: <URL>; rel="alternate"; hreflang="fr") — the sitemap approach is preferred for large multilingual sites because it does not require PHP execution per page to output the tags. Popular multilingual WordPress plugins (WPML, Polylang) generate hreflang tags automatically when configured correctly. The structured data post covers JSON-LD for rich results; hreflang ensures search engines serve the geographically correct version of those rich results.
Problem: A WordPress site has English, French, and Spanish versions — after 6 months, Google Search Console shows the French and Spanish pages have “Duplicate without user-selected canonical” warnings, and French users are landing on the English version of pages from Google search results because the hreflang tags are missing on several post types and the canonical tags conflict with the hreflang declarations.
Solution: Implement hreflang tags programmatically via wp_head for all page types, ensure reciprocal links between all language versions, add x-default, and use a language-to-URL mapping stored in post meta to retrieve the corresponding URLs for each language version.
// Output hreflang tags for all page types via wp_head
add_action( 'wp_head', 'myplugin_output_hreflang_tags', 5 );
function myplugin_output_hreflang_tags(): void {
$alternates = myplugin_get_hreflang_alternates();
if ( empty( $alternates ) ) return;
foreach ( $alternates as $lang => $url ) {
printf(
'' . PHP_EOL,
esc_attr( $lang ),
esc_url( $url )
);
}
}
/**
* Build the hreflang alternates map for the current page.
* Returns [ 'en' => 'https://...', 'fr' => 'https://...', 'x-default' => 'https://...' ]
*/
function myplugin_get_hreflang_alternates(): array {
// Language configuration: slug => hreflang code
$languages = [
'en' => 'en',
'fr' => 'fr',
'es' => 'es',
];
$default_lang = 'en';
$alternates = [];
if ( is_singular() ) {
$post_id = get_queried_object_id();
foreach ( $languages as $lang_slug => $hreflang ) {
if ( $lang_slug === $default_lang ) {
// Current language: use canonical URL
$url = get_permalink( $post_id );
} else {
// Translated post ID stored in post meta by the multilingual plugin
$translated_id = get_post_meta( $post_id, "_translation_{$lang_slug}_id", true );
if ( ! $translated_id ) continue;
$url = get_permalink( (int) $translated_id );
}
if ( $url ) {
$alternates[ $hreflang ] = $url;
}
}
} elseif ( is_front_page() || is_home() ) {
foreach ( $languages as $lang_slug => $hreflang ) {
$alternates[ $hreflang ] = home_url( $lang_slug === $default_lang ? '/' : "/{$lang_slug}/" );
}
} elseif ( is_tax() || is_category() || is_tag() ) {
$term = get_queried_object();
foreach ( $languages as $lang_slug => $hreflang ) {
$translated_term_id = get_term_meta( $term->term_id, "_translation_{$lang_slug}_term_id", true );
if ( ! $translated_term_id && $lang_slug !== $default_lang ) continue;
$term_obj = $translated_term_id ? get_term( (int) $translated_term_id, $term->taxonomy ) : $term;
if ( $term_obj && ! is_wp_error( $term_obj ) ) {
$url = get_term_link( $term_obj );
if ( ! is_wp_error( $url ) ) {
$alternates[ $hreflang ] = $url;
}
}
}
}
// Add x-default pointing to the default language version
if ( isset( $alternates[ $languages[ $default_lang ] ] ) ) {
$alternates['x-default'] = $alternates[ $languages[ $default_lang ] ];
}
return $alternates;
}
NOTE: Three common hreflang mistakes cause Google to ignore the tags entirely: (1) Missing reciprocal links — if the English page links to the French page but the French page does not link back to the English page, Google invalidates the entire hreflang cluster; (2) Relative URLs — hreflang URLs must be absolute including the protocol and domain; (3) Mismatched canonical and hreflang — the canonical URL on each page must match the hreflang URL for that same language. Validate your hreflang implementation with Google Search Console → International Targeting → Language tab, and use the hreflang Testing Tool from Aleyda Solis to audit the entire site’s hreflang graph for broken reciprocal links.