Hreflang Checker
Check a page's hreflang tags and every page they name: return links, codes, redirects, noindex and canonicals, with the fix for each.
Any page of the set: we read its hreflang (HTML and HTTP header), then up to 20 pages it names.
How does hreflang work?
Each language or regional version of a page lists every version, itself included, with a language code and a full URL. Search engines then show the version that matches a searcher's language or region.
Google reads the set from any of three places: <link rel="alternate" hreflang="…"> tags in the page's <head>, an HTTP Link header, or xhtml:link entries in an XML sitemap. Google calls them equivalent and says using all three brings no benefit.
The versions can sit on other domains, and the same set goes on every page in it. The checker above reads one page's set, then fetches every page it names and reads theirs, so it sees the whole cluster rather than one page's half of it.
en/
<link rel="alternate" hreflang="en" href="https://www.example.com/" /> <link rel="alternate" hreflang="de" href="https://www.example.com/de/" /> <link rel="alternate" hreflang="fr" href="https://www.example.com/fr/" /> <link rel="alternate" hreflang="x-default" href="https://www.example.com/" />
de/de/
<link rel="alternate" hreflang="en" href="https://www.example.com/" /> <link rel="alternate" hreflang="de" href="https://www.example.com/de/" /> <link rel="alternate" hreflang="fr" href="https://www.example.com/fr/" /> <link rel="alternate" hreflang="x-default" href="https://www.example.com/" />
fr/fr/
<link rel="alternate" hreflang="en" href="https://www.example.com/" /> <link rel="alternate" hreflang="de" href="https://www.example.com/de/" /> <link rel="alternate" hreflang="fr" href="https://www.example.com/fr/" /> <link rel="alternate" hreflang="x-default" href="https://www.example.com/" />

The verdictHow many pairs point both ways. Google ignores a pair unless both pages link to each other.FindingsEach problem with the pages it's on and its source: Google's or Yandex's own page.What search engines readWhere Google and Yandex read hreflang: the HTML, the HTTP header, the sitemap.Return linksOne row per page: does it list the page in each column? Select a cell for both sides and the tag to add.What are hreflang return links?
If page A lists page B, page B must list page A. Google's rule is plain: if two pages don't both point to each other, the tags are ignored.
That's why a page-by-page check misses most hreflang errors: each page looks fine on its own. A missing return link usually comes from one template that wasn't updated, a version added later, or a CMS that lists only translated pages.
Each version must also list itself (no self-referencing hreflang is the second most common slip) and use one code for itself everywhere. In the matrix, the German page here never lists the English one, so Google ignores that pair; the Japanese page couldn't be checked from our server, so its cells say so instead of claiming an error.
- Lists it
- Doesn't list it
- Under another code
- Couldn't check
| Lists | en/en/ | de/de/ | fr/fr/ | es-419/es/ | ja/ja/ | it/it/ |
|---|---|---|---|---|---|---|
| en/en/ | ✓ | ✓ | ≠ | ✓ | ✓ | ✓ |
| de/de/ | ✗ | ✓ | ≠ | ✓ | ✓ | ✓ |
| fr/fr/ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| es-419/es/ | ✓ | ✓ | ≠ | ✓ | ✓ | ✓ |
| ja?/ja/ | ? | ? | ? | ? | ? | ? |
| it404/it/ | ? | ? | ? | ? | ? | ? |
- en/en/Lists✓itself✓de≠fr✓es-419✓ja✓it
- de/de/Lists✗en✓itself≠fr✓es-419✓ja✓it
- fr/fr/Lists✓en✓de✓itself✓es-419✓ja✓it
- es-419/es/Lists✓en✓de≠fr✓itself✓ja✓it
- ja?/ja/Lists?en?de?fr?es-419?itself?it
- it404/it/Lists?en?de?fr?es-419?ja?itself
Which hreflang codes are valid?
An ISO 639-1 language (de), optionally with an ISO 3166-1 alpha-2 region (de-AT) or an ISO 15924 script (zh-Hant). Case doesn't matter. A region on its own isn't valid.
Spanish shows the usual traps. es reaches Spanish speakers everywhere; es-ES and es-MX target Spain and Mexico; es-419, the code for Latin America, isn't supported by Google, so list the countries or use es. The United Kingdom is GB, never UK: Google ignores reserved codes in part.
Yandex takes the same languages and regions, plus Russian regions from ISO 3166-2:RU, which Google doesn't support.
| Code | What it means | |
|---|---|---|
| en | Supported | English, any region |
| en-GB | Supported | English, United Kingdom |
| en-UK | Not supported | UK is reserved: write GBUse en-GB |
| es | Supported | Spanish, any region |
| es-ES | Supported | Spanish, Spain |
| es-MX | Supported | Spanish, Mexico |
| es-419 | Not supported | Latin America isn't an ISO 3166-1 regionUse es |
| zh-Hant | Supported | Chinese in Traditional script |
| zh-TW | Supported | Chinese, Taiwan |
| en_US | Not supported | An underscore isn't a codeUse en-US |
| GB | Not supported | A region alone isn't valid |
| x-default | Supported | The page for unmatched languages |
Do I need x-default?
It's optional. x-default names the page for searchers whose language or region the set doesn't list; Google designed it for language selector pages.
Point it at the page you'd want an unmatched visitor to see: a language picker, or the version you treat as international. It counts like any other entry, so the page it names should list the set too.
Yandex reads x-default in the HTML markup as well.
| Searcher's language | Version it matches |
|---|---|
| German (Austria) | de-AT |
| German (Germany) | de |
| English | en |
| Portuguese (Brazil) | x-default |
HTML, HTTP header or sitemap: where should hreflang go?
Pick one. Google reads all three the same way; Yandex reads only the HTML markup and no longer reads language versions from sitemaps.
Use the HTML <head> for ordinary pages. The HTTP Link header suits PDFs and other files without a head. Sitemaps keep large sets out of the page: each <url> lists every version with xhtml:link, and a sitemap can only list URLs below the folder it's served from.
This checker reads the HTML and the header. It doesn't open sitemaps yet: if your hreflang lives there, check the file with the Sitemap Checker.
| Method | Yandex | |
|---|---|---|
| HTML <link> | Reads it | Reads it |
| HTTP Link header | Reads it | Not in its docs |
| XML sitemap | Reads it | Not supported |
How do hreflang and canonical work together?
Each version's canonical should be itself, and the hreflang set should name those canonical URLs. A version whose canonical names another language's page asks Google to treat that page as the original.
Google asks for a canonical in the same language as the page. A rel=canonical that carries hreflang is ignored as a canonical, and a redirect is itself a strong canonical signal, so the tags should name the URL a redirect ends at. The checker flags alternates that redirect, carry noindex, or name another URL as canonical.
Right
/de/ canonical → /de/ hreflang de → /de/
The German page is its own canonical, and the set names it.
Wrong
/de/ canonical → /en/ hreflang de → /de/
The German page names the English one as canonical: it asks Google to treat the English page as the original.
Where does hreflang come from in WordPress, Shopify and Wix?
From the multilingual plugin or the platform, not from SEO plugins on their own. Each documents where its tags go:
When the checker finds a missing return link on a WordPress site, the usual cause is a page that wasn't translated or linked as a translation in the plugin, so the plugin doesn't list it. Check the pair in the plugin's translation screen, then run the check again.
| Product | Where the tags go | x-default |
|---|---|---|
| WPML | The page <head>, or the Yoast SEO or Rank Math sitemap when WPML SEO's sitemap is on (not both) | Yes |
| Polylang | The page <head> | Home page only |
| TranslatePress | The page <head> | A setting |
| Weglot | The page <head> | Not by default |
| Shopify | Every page's <head> and the sitemap, for markets with their own domain, subdomain or subfolder | Yes |
| Wix | The page's code, by default | Yes |
Hreflang generator: write the tags
Switch the tool to Generate tags, list each version's code and URL, and copy the same set as HTML tags, one HTTP Link header or sitemap entries.
Each code is checked as you type, with the fix Google expects (en-UK becomes en-GB, es-419 becomes es); a row is written only when its code and full URL are valid. Put the same set on every page it lists, then press Check these pages to read them back.
<link rel="alternate" hreflang="en" href="https://www.example.com/" /> <link rel="alternate" hreflang="de" href="https://www.example.com/de/" /> <link rel="alternate" hreflang="es" href="https://www.example.com/es/" /> <link rel="alternate" hreflang="x-default" href="https://www.example.com/" />
Sources
- Google Search Central: Tell Google about localized versions of your page and How to specify a canonical URL.
- Yandex Webmaster: Localized page versions.
- Vendor docs: WPML, Polylang, TranslatePress, Weglot, Shopify, Wix.
- RFC 8288 (the HTTP Link header).
Hreflang Checker FAQ
How do I check hreflang tags?
Is a hreflang validator the same as a checker?
What does "no return tags" mean?
Does a page need to reference itself in hreflang?
Which hreflang code should I use for Spanish?
es for Spanish speakers anywhere, or a country: es-ES for Spain, es-MX for Mexico, es-US for the United States. Google doesn't support es-419 (Latin America); list the countries or use es.Should hreflang be in the sitemap or in the HTML?
Why does the checker say it couldn't check a page?
Why are only 20 alternates checked?
Missing something?Suggest a feature or a new tool
Related tools
Meta Tag Checker
Check a page's title, meta description, canonical, hreflang, Open Graph and X card tags, with Google and social previews
Sitemap Checker
Find and validate a website's XML sitemap: syntax, URL limits, lastmod dates, robots.txt conflicts and broken or redirecting URLs
Redirect Checker
Follow every redirect hop by hop: 301/302 chains, timing and bulk checks
HTTP Header Checker
Response headers exactly as sent, with a Mozilla Observatory security grade