How to Implement hreflang Tags for Multilingual WordPress

You translated your entire WordPress site into six languages. The .po files are clean, the .mo files are compiled, every string renders perfectly in French, German, and Japanese. Yet weeks after launch, you check Google Search Console and find something baffling: your French page is ranking for German queries, your Spanish-Mexico page is being served to searchers in Spain, and two of your language versions are quietly cannibalizing each other in the rankings. The content is fine. The problem is that Google has no idea which version belongs to which audience.
This is exactly what hreflang WordPress tags are designed to fix. Hreflang is the signal that tells search engines which language and regional version of a page to serve to which user. Get it wrong and you suffer duplicate-content dilution, wrong-language results, and wasted crawl budget. Get it right and each translated page reaches the audience it was written for. This guide covers the syntax, the difference between language and language-region codes, the mandatory x-default, return-tag reciprocity, and three practical ways to implement it in WordPress.
What hreflang Actually Does
Hreflang does not translate anything. It is a relationship map. When you have the same page in multiple languages, hreflang annotations tell Google "this URL is the English version, this one is the German version, this one is for Spanish speakers in Mexico." Google uses that map to serve the correct version in search results based on the user's language settings and location.
The annotation lives in the <head> of your HTML as a set of <link> elements. Each element names a target page and the language (and optionally region) it serves. Here is a correct set of hreflang tags for a page available in US English, UK English, and Mexican Spanish, with a default fallback:
<link rel="alternate" hreflang="en-US" href="https://example.com/en-us/pricing/" />
<link rel="alternate" hreflang="en-GB" href="https://example.com/en-gb/pricing/" />
<link rel="alternate" hreflang="es-MX" href="https://example.com/es-mx/pricing/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/pricing/" />
Every page in the set should output this same block, listing every alternate including itself. That last point trips up most people, and we will return to it.
Why This Matters for SEO
Without hreflang, Google treats your translated pages as near-duplicates competing for the same keywords. The result is dilution: ranking signals split across versions instead of consolidating. With correct hreflang, each version inherits the authority of the set as a whole and surfaces to the right searchers. This is one of the highest-leverage SEO moves a multilingual site can make, and it complements rather than replaces the actual content translation in your .po files. We cover the content side in depth in our guide to how translating PO files impacts Google rankings.
Language vs Language-Region Codes
Which should you use, en or en-GB? Use the plainest code that is true. If your English content is generic and you do not maintain separate British and American versions, use en alone. Only add a region subtag when you genuinely serve different content to different countries.
Hreflang values follow the format language or language-region. The language part is an ISO 639-1 code (en, es, de, pt). The optional region is an ISO 3166-1 Alpha 2 country code (US, GB, MX, BR). So:
entargets all English speakers regardless of country.en-GBtargets English speakers in the United Kingdom.estargets all Spanish speakers.es-MXtargets Spanish speakers in Mexico specifically.
A critical detail: the region subtag is a country, not a language variant. es-MX does not mean "Mexican Spanish dialect," it means "Spanish, shown to users in Mexico." Google still serves it based on the user's location. If you maintain regional dialects in your translations, the region code is how you route them. We dig into the linguistic side of this in our guide to regional Spanish and Portuguese variants.
A Common Wrong Example
Here is a broken hreflang block we see constantly:
<link rel="alternate" hreflang="en-uk" href="https://example.com/uk/" />
<link rel="alternate" hreflang="sp" href="https://example.com/es/" />
<link rel="alternate" hreflang="pt-PT_BR" href="https://example.com/pt/" />
Three errors. en-uk is invalid because the country code for the United Kingdom is GB, not UK. sp is not a language code at all; Spanish is es. And pt-PT_BR mixes two regions with an underscore that has no meaning in hreflang. Search engines silently ignore malformed values, so you get no error and no benefit, just hours of debugging wondering why nothing changed.
Note that hreflang uses a hyphen (en-GB), while WordPress locale files use an underscore (en_GB). Do not copy your .po filename codes directly into hreflang tags.
The x-default and Return-Tag Reciprocity
Two rules cause more silent hreflang failures than anything else: the missing x-default and broken reciprocity.
The x-default value tells Google which page to serve when no other language matches the user. It is your fallback, typically your language selector or your primary-market homepage. It is not strictly mandatory by spec, but in practice you should always include it. Without it, a Portuguese-speaking visitor for whom you have no Portuguese version gets a coin-flip instead of a deliberate default.
Return-tag reciprocity is non-negotiable. Every page in an hreflang set must point back to every other page, including itself. If your English page lists German as an alternate, your German page must list English as an alternate. If the German page omits the return reference, Google distrusts the entire relationship and ignores the annotations.
<!-- On the German page, the set must still list ALL versions -->
<link rel="alternate" hreflang="de" href="https://example.com/de/about/" />
<link rel="alternate" hreflang="en" href="https://example.com/en/about/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/about/" />
The self-reference (de pointing to the German page while viewing the German page) is required, not optional. A set of five language versions means each of the five pages outputs all five <link> tags plus x-default.
Three Ways to Implement hreflang in WordPress
You have three practical options, ranging from full manual control to fully automated.
Method 1: Manual via wp_head
For a small static set of pages, you can inject hreflang tags directly through the wp_head action in your theme's functions.php:
add_action( 'wp_head', function () {
if ( ! is_page( 'pricing' ) ) {
return;
}
$alternates = array(
'en-US' => 'https://example.com/en-us/pricing/',
'es-MX' => 'https://example.com/es-mx/pricing/',
'x-default' => 'https://example.com/pricing/',
);
foreach ( $alternates as $lang => $url ) {
printf(
'<link rel="alternate" hreflang="%s" href="%s" />' . "\n",
esc_attr( $lang ),
esc_url( $url )
);
}
} );
This gives you precise control but does not scale. Maintaining reciprocity by hand across hundreds of posts is a maintenance trap.
Method 2: A Multilingual Plugin
If you run Polylang, WPML, or TranslatePress, hreflang is generated automatically based on your translation relationships. This is the right answer for most sites because the plugin already knows which post is the translation of which, so reciprocity and x-default are handled for you. The cost is the runtime overhead these plugins add, which is the trade-off we examine in our comparison of manual versus AI translation approaches.
Method 3: XML Sitemap with xhtml:link
Instead of putting hreflang in every page's <head>, you can declare the relationships in your XML sitemap using the xhtml:link namespace. Each <url> entry lists all its language alternates:
<url>
<loc>https://example.com/en/about/</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/about/" />
<xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/about/" />
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/about/" />
</url>
This keeps your page HTML lean and centralizes the language map in one crawlable file, which Google reads happily. Most SEO plugins can generate this automatically.
hreflang Complements Translation, It Does Not Replace It
Here is the point everyone misses: hreflang is worthless if the underlying pages are not actually translated. The tag tells Google "this is the German version," but if the German URL still shows English plugin strings, untranslated checkout labels, or half-finished .po files, you have simply told Google to serve broken content to German users with full confidence.
Real multilingual SEO is two layers. The content layer is your translated .po, .pot, .json, and .xliff files driving every theme and plugin string into the target language. The signal layer is hreflang routing each finished version to its audience. Both have to be solid. A pristine hreflang set on top of a 40-percent-translated site just guarantees a worse experience faster.
This is where keeping the translation layer clean matters most. When you translate your interface files, placeholders like %s, %1$s, and {name} must survive intact, or your "translated" German page renders with literal %s tokens that look broken to both users and search engines. A gettext-aware translation pipeline with Syntax Locking protects those tokens automatically, so every alternate page you point hreflang at is genuinely production-ready. Because it runs in the cloud with Smart Batching, you can push an entire multi-locale site through it without installing translation plugins that bloat the runtime that serves those very pages.
Get the content right first, then let hreflang do its job. The two together are what consolidate ranking signals, eliminate wrong-language results, and put each translated page in front of the people who can read it.
Ready to fix wrong-language search results at the source? Try SimplePoTranslate free — no credit card required. Translate your
.po,.pot, and.jsonfiles on the free tier, then point your hreflang tags at pages that are actually ready for every market.