Docket / Fix it / Invalid hreflang codes

hreflang values that are not valid language codes

An audit came back saying an hreflang value is not a valid language code, or you are looking at a tag you suspect is wrong and want to know before you edit a template that ships on every page. The question splits in two, and the useful half is the second: what makes a value invalid, and how much of that can a crawler actually tell you.

The shape a value has to take

An hreflang value is a language subtag, then optionally a script, then optionally a region, joined by hyphens. Docket's intl.hreflang_codes check matches exactly that and nothing else:

language   2 or 3 letters      en  de  ceb
script     4 letters, optional  Hant  Cyrl
region     2 letters, or 3 digits, optional   GB  419

Per RFC 5646 section 2.2.1 — the document BCP 47 points at — two-character language subtags "were defined in the IANA registry according to the assignments found in the standard" ISO 639-1, and three-character ones come from the other parts of that standard. Per section 2.2.4, two-letter regions come from ISO 3166-1 alpha-2 and "three-character region subtags consist solely of digit (number) characters" from the UN M.49 statistical codes. Scripts are ISO 15924. All read at rfc-editor.org on 16 September 2026.

The value x-default is skipped before any of this runs, which it has to be: it is a reserved token, not a language, and the pattern above would reject it.

Things that fail the shape test and are reported: en_US with an underscore, which is how locales are written in a lot of codebases and never how an hreflang value is written; en-GBR, the three-letter country code; anything with a stray space or a trailing comma from a templating loop.

Three findings, not one

Values that clear the shape test are sorted into three separate findings, and the severity tells you which pile you are in.

intl.hreflang_bad_code, at MEDIUM. The shape is wrong, or the region is one of 3 strings Docket holds as known-wrong. Effort is marked trivial, because it is a find-and-replace in one template.

intl.hreflang_deprecated_lang, at LOW. A well-formed value naming a real language by a subtag the registry has retired.

intl.hreflang_unrecognised_lang, at NOTICE. A well-formed value whose language Docket's table does not contain. It carries the tool-limit flag, scores nothing, and stays off the fix list — it is a statement about Docket, not about your markup.

What this check does not validate

Put first, because it is the difference between a clean result and a correct one.

The region is not checked against the country list. The finding's own text says the country "must be a real ISO 3166-1 alpha-2 code", and that is wider than the code behind it. Docket holds 3 region strings: uk, which it rewrites to GB; eu, which is not a country; and en, which is a language sitting in the region slot. Everything else that is two letters passes. Running the check over en-XX and en-QQ — both user-assigned in the country standard, neither a country — produces no finding at all. Note too that the language-in-the- region-slot case is caught only when that language is English: de-en is reported, de-fr is not.

Capitalisation is not an error, and Docket will not pretend it is. RFC 5646 section 2.1.1 is unambiguous: "At all times, language tags and their subtags, including private use and extensions, are to be treated as case insensitive". The standard recommends uppercase regions as a convention, and a convention is not a defect. This is in the code because Docket got it wrong in public — an earlier table mapped us to US and za to ZA, and because the region is lowercased before the lookup, two professionally run sites were told en-US was broken and handed en-US as the correction. One of them serves every one of its tags lowercase and they work.

Only tags in crawled HTML are seen. The values come from link rel="alternate" elements in the pages the crawl fetched. Google's localized-versions documentation describes two other ways to declare the same thing — an HTTP Link: header, which it recommends for non-HTML files, and annotations in an XML sitemap. Neither reaches this check, so a site that declares hreflang in its sitemap gets silence here rather than a clean bill.

The count is distinct codes. Each value string is judged once for the whole crawl. A template repeating one wrong code on every page produces one entry, not one per page, and the URL shown beside it is the first page the crawler happened to see it on rather than the only one to fix.

The one region typo worth its own paragraph

en-uk is the common one, and it is genuinely wrong rather than stylistically wrong. In the IANA language subtag registry — the list BCP 47 defers to, read on 16 September 2026 at the URL below, File-Date 2026-08-08 — the record reading Description: United Kingdom has the subtag GB. There is no UK record of any type. Google's localized-versions documentation states the consequence directly: "Only language codes listed in ISO 639-1 and region codes listed in ISO 3166-1 Alpha 2 are supported; other codes that aren't listed in those standards, such as es-419, aren't supported."

Which raises the divergence you should know about before you trust a pass. Docket accepts es-419, because BCP 47 explicitly permits UN M.49 numerics in the region slot, and that sentence of Google's says Google does not. Both are accurately reported here: the value is well-formed under the standard and unsupported by one search engine. Docket checks the standard, so a value like that leaves the audit silently, and silence is not a promise that Google honours it.

Retired subtags: a tidy-up, not a fault

6 two-letter language subtags in the registry carry both a deprecation and a replacement, and every one of them still turns up in production. The registry's own pairings, read on 16 September 2026:

in -> id   Indonesian      iw -> he   Hebrew
ji -> yi   Yiddish         jw -> jv   Javanese
mo -> ro   Moldavian       bh -> bih  Bihari languages

This finding exists because of a specific piece of dishonesty. A global retailer publishing in-ID across its hreflang block was told Docket did not recognise the language — true of Docket's table, and useless, because in is not an unknown language. It is the superseded subtag for Indonesian and the registry names its replacement. A notice saying "our fault, nothing to do" was sitting on something with a documented answer.

What the finding deliberately does not say is that search engines ignore the old form. Nobody outside those companies can measure that, so the claim is absent and the severity is LOW. The recommendation is the registry's: use the current subtag, and change the matching entries on the pages at the other end in the same edit, or the pair stops lining up. That pairing rule is the subject of how to fix hreflang return tags, and it is the more expensive failure of the two.

When Docket says it does not recognise a language

Docket's language table holds 185 two-letter subtags: the 184 current in the registry as of File-Date 2026-08-08, plus bh, which is deprecated rather than unknown. A value whose language is not in that table is well-formed, may be entirely correct, and gets a notice that scores zero.

Two real cases explain why it is a notice. Cebuano is ceb — it has no two-letter form at all, so no two-letter table can ever contain it, and RFC 5646 section 2.2.1 permits the three-letter subtag. Aragonese is an, an ordinary current subtag that was simply missing from an earlier, shorter version of the table; a real multilingual site was told its language was unrecognised, and the fix was to widen the table rather than to soften the wording. Docket cannot tell your typo from our short list, so it says which of the two it is claiming.

The honest use of that notice: read the codes it lists, and check any you do not recognise yourself against the IANA language subtag registry. That file is plain text and searchable in a browser.

Checking a value without running anything

Pull the tags off a page and read the values in one column:

curl -s https://example.com/ | grep -o 'hreflang="[^"]*"' | sort -u

Then, for each value you are unsure of, search the registry file for the language subtag and for the region separately. A subtag record that carries a Deprecated line names its replacement on the next line. A region you cannot find at all is the en-uk case: the tag is discarded and the page it pointed at is not connected to anything.

If the codes all check out and the set still is not working, the fault is one page over — either the cluster is not reciprocal, or the declared language of the page contradicts its own prose. hreflang and html lang mismatch separates those. What Docket checks lists the rest of the indexing lane these findings sit in.

Everything on this page describes the build shipped as v1.3.70, read in the engine source rather than from the manual.

Download Docket

Common questions

Is en-UK a valid hreflang value?

No. The region subtag comes from the country standard, where the United Kingdom is GB, and there is no UK record of any type in the IANA language subtag registry. Google's own documentation says region codes outside that standard are not supported, so en-uk connects the page to nothing.

Does hreflang have to be uppercase for the country?

No. The standard states that language tags and their subtags are to be treated as case insensitive at all times. Uppercase regions are a recommended convention, not a requirement, and af-za and af-ZA are the same tag. Docket reports neither.

Why does Docket say it does not recognise a language code I know is real?

Because its language table is two-letter only, and some languages have no two-letter form — Cebuano is the usual example. That finding is a notice carrying a tool-limit flag: it scores nothing and stays off the fix list, because it is a statement about the tool rather than about your markup.

Is a deprecated language subtag like in-ID broken?

It is out of date rather than broken. The registry marks it deprecated and names the current subtag, id for Indonesian. Whether a given search engine still honours the old form cannot be measured from outside, so Docket reports it at LOW as a tidy-up and makes no claim about what any engine does with it.

Will Docket catch every invalid country code in my hreflang tags?

No, and this is the limit worth knowing. It validates the shape of the value and holds three region strings it knows are wrong; a two-letter region outside that short list passes even when no such country exists. Check unfamiliar regions against the country standard yourself.

Does Docket read hreflang from my XML sitemap?

Not in this check. The values it judges come from link rel=alternate elements in the HTML it crawled. Google also supports hreflang in an HTTP Link header and in XML sitemap annotations, and a site declaring them that way gets no finding here rather than a clean result.