
The setup that most often kills a multilingual WordPress site is not the hreflang tags. It is the canonical tag sitting next to them.
A common sequence: someone launches Spanish and German versions, the SEO plugin applies a site-wide canonical, and every translated page now declares the English original as its canonical URL. Google follows the instruction, consolidates everything into the English page, and the translations drop out of the index. The hreflang annotations are still there, still valid-looking, and now completely ignored — because they point at pages Google has decided are duplicates.
That is the failure this guide is built around. It covers the markup, the codes Google actually accepts (two popular ones are rejected), the canonical rule that keeps translations indexed, and how to verify reciprocity before you ship.
What Hreflang Does — and What It Can’t
Hreflang tells Google which language and regional version of a page to serve to which user. It is an annotation, not a translation mechanism, and not a ranking boost.
Three things it does not do:
- It does not detect your page’s language. Google’s localized versions documentation is explicit: Google “doesn’t use hreflang or the HTML lang attribute to detect the language of a page; instead, we use algorithms to determine the language.”
- It does not fix duplicate content. Two pages in the same language with the same content are duplicates, and hreflang won’t separate them. That is a canonical or redirect decision.
- It does not geo-target. A page annotated
en-GBcan still rank in Australia. Hreflang expresses which version suits a user; it doesn’t fence a page into a country.
What it does do is prevent the wrong-language page from being served when you have several genuine versions of the same content. That is a real problem worth solving — but only after the canonical and indexing layers are correct.
The Rule That Breaks Most Setups: Reciprocity
Google’s guidance is unambiguous, and it is the sentence to keep in mind while building anything: “Each language version must list itself as well as all other language versions.”
Two failures follow from ignoring that:
- No self-reference. The English page lists Spanish and German but not itself. Google wants the complete set on every page, including the page you are on.
- One-way links. If the English page points at the Spanish page but the Spanish page doesn’t point back, “the tags will be ignored.” Google’s documentation explains why: otherwise any site could claim your page as an alternate version of theirs.
There is one useful allowance. If a full bidirectional set is hard to maintain, Google says you can “omit some languages on some pages” and it “will still process the ones that point to each other.” Partial coverage is survivable; a broken pair is not.
Alternate URLs must also be fully qualified, including the scheme:
<!-- correct -->
<link rel="alternate" hreflang="es-MX" href="https://example.com/mx/precio/" />
<!-- wrong: relative, and scheme-relative -->
<link rel="alternate" hreflang="es-MX" href="/mx/precio/" />
<link rel="alternate" hreflang="es-MX" href="//example.com/mx/precio/" />
The Tags, Written Out
For an article that exists in English, Mexican Spanish, and German, every one of those three pages carries this same block (with href values unchanged — the set is identical on all three):
<link rel="alternate" hreflang="en" href="https://example.com/guides/pricing/" />
<link rel="alternate" hreflang="es-MX" href="https://example.com/mx/guias/precios/" />
<link rel="alternate" hreflang="de" href="https://example.com/de/anleitungen/preise/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/guides/pricing/" />
Note what is in there:
- The current page is included. The Spanish page includes the Spanish URL.
- The URLs differ in slug, not just prefix.
/guides/pricing/and/mx/guias/precios/are genuinely different paths. This is where hand-written tag blocks fall apart — more on that below. x-defaultis present. It is the fallback for users whose language or region doesn’t match any variant. Google specifically recommends it for “language/country selectors or auto-redirecting home pages.”
x-default can point at one of the existing versions, usually the primary or English one, or at a language-selector page. What it should not do is point at a URL that redirects somewhere else.
The Two Codes Google Silently Rejects
The hreflang value takes a language code in ISO 639-1, optionally followed by a region code in ISO 3166-1 Alpha 2:
hreflang="en" language only
hreflang="en-GB" language + region
Two popular values are not supported, and both fail quietly — no error, no warning, just an annotation that does nothing:
en-UK. The United Kingdom’s ISO 3166-1 code is GB, not UK. Google’s troubleshooting section lists UK among the codes that are “reserved for something else” and says using them “doesn’t have an effect on Google Search.” The correct value is en-GB.
es-419. This is the CLDR code for Latin American Spanish, and it is widely used in translation tooling. Google’s documentation names it directly: “other codes that aren’t listed in those standards, such as es-419, aren’t supported.” If you need Latin America as a group, either target countries individually (es-MX, es-AR) or use plain es.
Two more rules from the same page:
- A region alone is invalid.
hreflang="MX"means nothing; it must bees-MX. - Region codes go in uppercase. Values are case-insensitive, but Google recommends uppercase region codes, and consistency makes the sets easier to audit.
If you are unsure whether a code exists, check the IANA language subtag registry rather than a blog post.
The Canonical Rule That Protects Translations
This is the part that decides whether your multilingual content stays indexed.
Google’s canonical documentation gives the rule: when using hreflang, “make sure to specify a canonical page in the same language, or the best possible substitute language if a canonical page doesn’t exist for the same language.”
Which means:
| Setup | Result |
|---|---|
| Every language version self-canonicalises | Correct. Each version can be indexed |
| Every language version canonicalises to the English page | Broken. Translations are duplicates and drop out |
| Spanish page canonicalises to the Mexican Spanish page | Acceptable only if they are genuinely the same content |
A self-referencing canonical on each language version is the default you want. The Spanish page should declare the Spanish URL as canonical, and the German page the German URL.
The related mistake is in internal links. Google’s guidance is to link to the canonical URL rather than a duplicate — so your German navigation should point at German URLs, not at the English versions of the same pages. On WordPress this usually happens when a menu is built once in the default language and reused, or when a “switch language” link is the only link between versions.
Three Ways to Implement It in WordPress
1. Let the multilingual plugin do it
WPML, Polylang, TranslatePress and Weglot all manage language URLs, and most output hreflang annotations for you. If you are choosing one, our roundup of WordPress translation plugins covers the trade-offs.
The risk with this route is duplication. When a multilingual plugin and an SEO plugin both output alternate links, the page can end up with two sets — often one pointing at prefixed URLs and one at translated slugs. Google sees conflicting signals and may ignore both. Check the rendered source, not the plugin settings screen:
curl -s https://example.com/de/anleitungen/preise/ | grep -o 'hreflang="[^"]*"'
One clean set per page. If you see each language twice, one of the two plugins has to stop outputting tags.
The second risk is stale mappings. Translate a post, then change its slug, and the plugin’s annotation usually follows — unless the plugin stores the alternate URL as a fixed string. Verify after any bulk slug change.
2. Output them manually from the translation plugin’s API
Hand-writing the block in a theme is where multilingual sites get hurt, because the tags must list URLs for pages that may have different slugs, and a string-replace on the path will produce the wrong URL. If you go this route, generate each alternate from the translation plugin’s own post-mapping function rather than constructing paths, and add all of them on every variant, including the current page.
The output you are aiming for is exactly the block shown earlier. The implementation detail worth insisting on: one function, one source of truth, every variant rendered from the same map. Two templates producing tags independently is how partial sets appear.
3. Use an XML sitemap
For sites with many languages or many posts, the sitemap method is easier to keep correct than head tags, and Google supports it as an equal alternative. Our guide to multilingual sitemaps in WordPress covers the WordPress-specific setup; the structure Google requires looks like this:
<url>
<loc>https://example.com/de/anleitungen/preise/</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/guides/pricing/" />
<xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/anleitungen/preise/" />
<xhtml:link rel="alternate" hreflang="es-MX" href="https://example.com/mx/guias/precios/" />
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/guides/pricing/" />
</url>
The xhtml namespace must be declared on the urlset element (xmlns:xhtml="http://www.w3.org/1999/xhtml"), and each <url> entry must list every alternate including itself. Those <xhtml:link> children do not count toward the sitemap’s URL limit, which is why this method scales.
If sitemaps in general are unfamiliar, the sitemap examples and best practices cover the basics before you add the hreflang layer.
The WordPress Traps That Cause “Missing Return Links”
Most reciprocity failures on WordPress are not annotation errors. They are URL inconsistencies between pages.
Trailing slash mismatch. If the English page links to https://example.com/es/precios and the Spanish page’s canonical is https://example.com/es/precios/, the pair doesn’t match cleanly. Pick a permalink style and enforce it. WordPress’s canonical redirect will send visitors to the right form, but an alternate link pointing at a redirect is a weak signal.
www and scheme drift. http:// and https://, www and non-www are four different strings. Every href must use the exact host and scheme the page actually serves. A mixed set is the most common cause of “everything is tagged but Google ignores it.”
Automatic language redirects by IP or browser. If you reroute German visitors to /de/ based on Accept-Language, you also reroute Googlebot, and Google warns that it may then never crawl all your variations: the crawler “usually originates from the USA” and “sends HTTP requests without setting Accept-Language in the request header.” It sees the default version every time. Use a visible language selector, keep every version crawlable at its own URL, and let x-default handle the unmatched case.
Translations published as drafts or noindexed. A hreflang entry pointing at a noindexed page is a dead end. Publish the variant, keep it indexable, and only then add it to the set.
Machine-translated pages with no substance. Hreflang directs users to a version; it doesn’t make that version worth indexing. If a translation is thin, the annotation is asking Google to serve a page it may decline to index.
Verifying the Set Before You Ship
Reciprocity means checking pairs, not pages. A quick loop over each language URL, printing the alternates it declares:
for u in \
https://example.com/guides/pricing/ \
https://example.com/mx/guias/precios/ \
https://example.com/de/anleitungen/preise/ ; do
echo "== $u"
curl -s "$u" | grep -o 'hreflang="[^"]*" href="[^"]*"'
done
Then compare by hand. For every A → B line, there must be a B → A line in another block, and each block must contain its own URL. If a language appears in one set and not the other, that pair is broken.
Also confirm the targets resolve:
curl -sI https://example.com/de/anleitungen/preise/ | head -n 1
A 200 is required. A 301 means the annotation points at a redirect, and a 404 means you are advertising a page that doesn’t exist.
Search Console has no dedicated hreflang report, so this source-level check is the verification step. Google’s documentation lists third-party validators too, but the loop above answers the only question that matters: does every page declare the same complete set?
What Hreflang Won’t Fix
A few expectations worth correcting before you spend a week on annotations:
- It won’t make a weak translation rank. It selects among versions; it doesn’t improve them.
- It won’t consolidate same-language duplicates. Two English pages competing with each other need a canonical or a merge, not hreflang.
- It won’t replace a language selector. Users need a way to switch; hreflang only informs search results.
- It won’t rescue a site that isn’t indexed. If the translations are missing from the index entirely, start with why pages don’t get indexed — hreflang is downstream of that.
If the multilingual structure is part of a broader international push, the planning side is covered in going international with your website, and the on-page fundamentals in our WordPress SEO checklist.
Setup Checklist
- Decide the URL pattern per language (subdirectory, subdomain, or separate domain) and keep it consistent
- Confirm every language version is published, indexable, and returns
200 - Give each version a self-referencing canonical
- Add the complete alternate set to every version, including itself
- Use ISO 639-1 for language and ISO 3166-1 Alpha 2 for region — uppercase region, never region alone
- Replace
en-UKwithen-GB, and avoides-419 - Add
x-defaultfor the unmatched-language fallback - Use absolute URLs with the exact scheme and host
- Disable automatic IP/language redirects so every version stays crawlable
- Run the reciprocity loop above and fix any pair that isn’t mutual
FAQ
Does WordPress support hreflang out of the box?
No. Core has no hreflang output. Multilingual plugins (WPML, Polylang, TranslatePress, Weglot) generate it, or you add it through a sitemap or custom code. WordPress core’s canonical output only covers singular views.
Should hreflang pages canonicalise to themselves?
Yes. Each language version should have a self-referencing canonical. Google’s guidance is to specify a canonical page in the same language, so pointing every translation at the English original removes those translations from the index.
Is es-419 valid for hreflang?
Not for Google. It is a legitimate CLDR code, but Google’s documentation names es-419 as unsupported because it isn’t in ISO 639-1 plus ISO 3166-1 Alpha 2. Target individual countries (es-MX, es-AR) or use plain es.
Is en-UK correct?
No. The ISO 3166-1 code for the United Kingdom is GB. Google lists UK among reserved codes that have no effect. Use en-GB.
Do I need hreflang if the content is translated but the URLs are the same?
If the URL is identical and the language is chosen by cookie or browser settings, hreflang can’t help — there is only one URL to annotate. Use distinct URLs per language so each version can be indexed and linked independently.
What is x-default used for?
It marks the fallback page for users whose language or region doesn’t match any variant — typically a language selector or the primary-language page. Google recommends it for language/country selectors and auto-redirecting home pages.
How many hreflang tags can one page have?
There’s no practical limit in the markup, but every additional language multiplies the pairs you must keep mutual. Past a handful of languages, move the annotations into a sitemap, where each entry is generated rather than hand-maintained.