Hreflang in WordPress: WPML, Polylang and Manual Tags
If you searched for hreflang WordPress because an audit tool flagged your multilingual site, here's the short version: WordPress itself does not write hreflang tags, and neither does your SEO plugin, on its own. The tags always come from a translation plugin (WPML, Polylang, TranslatePress, Weglot) or from code you added yourself. Once you know that, the confusing part of this whole topic goes away: you stop hunting through Yoast's settings for a switch that was never there.
Hreflang is a short piece of code that tells Google "this page has a version in another language, and here's where to find it." One page pointing to a translation without that translation pointing back is a pair Google ignores.
If you want the full mechanics of how the tags are built, we cover that in our explainer on how hreflang works. This article is about the WordPress-specific part: who is writing your tags, and how you check.
Does Yoast SEO Handle Hreflang for Multilingual Sites?
No. Yoast SEO does not output hreflang tags, and it never has. A Yoast staff member said it plainly on the WordPress.org support forum: "Yoast SEO has no features regarding multilingual sites, so we don't output hreflang meta tags." A related feature request on Yoast's GitHub, asking for hreflang in the sitemap when WPML is active, has sat open since 2017 without being built.
So what does a Yoast SEO multilingual setup actually rely on? Yoast still does its usual job: canonical tags, XML sitemaps, meta descriptions, readability checks. When Rank Math is paired with WPML's own SEO add-on, Rank Math's sitemap can carry the hreflang that WPML generates, but that is WPML doing the writing.
Neither SEO plugin authors hreflang tags on its own. You need a separate translation plugin for that job.
Which WordPress hreflang plugin is actually writing your tags
Before you touch a single setting, figure out which plugin is responsible. On a site that has collected two or three language plugins over the years, more than one might be doing this job at once, which is how duplicate tags happen. Here's how the main options compare.
| Plugin | Writes HTML tags | Writes sitemap hreflang | x-default behavior | Where the setting lives |
|---|---|---|---|---|
| WPML | Yes, if enabled | Yes, default since mid-2025 | Choosable per language setup | WPML > Languages > SEO Options |
| Polylang (free) | Yes, automatic | No | Homepage only | Languages > Settings, automatic once languages are added |
| Polylang Pro | Yes, automatic | No | Homepage, plus a code filter for other pages | Same as free, plus filter hooks |
| TranslatePress | Yes, on every non-excluded page | No, by default | On by default, can be turned off | Settings > Advanced > SEO Pack |
| Weglot | Plugin-generated, verify in page source | No, by default | Depends on setup | Weglot dashboard |
| No translation plugin | Only if you add code | Only if you add code | Only if you add code | Your theme's functions.php or a small custom plugin |
Notice that a wordpress hreflang plugin choice isn't just about translating your content. It also decides where the hreflang tags physically live: in the page's HTML head, in the XML sitemap, or in both. That distinction matters more than it sounds, because it's exactly where WPML changed behavior in 2025, which we'll get to next.
Setting up hreflang with WPML
WPML hreflang generation is automatic once you've set up your languages, but there is one detail that trips up a lot of owners who updated recently. As of WPML SEO 2.2.0, released in mid-2025, the default location for hreflang moved to the XML sitemap. The HTML head tags became an opt-in setting rather than the default. If you upgraded WPML sometime in 2025 and your hreflang tags vanished from the page source, this is almost certainly why, and a WPML support thread from July 2025 confirms it.
If your site now leans on the sitemap for this job, our guide to hreflang in an XML sitemap covers the format Google expects there.
- Go to WPML > Languages in your WordPress dashboard, then open the SEO Options tab. You should see a setting called "Display alternative languages in the HEAD section." If that toggle is off, your hreflang tags are in the sitemap only, which is valid, just not what older guides expect you to see in the page source.
- Turn that toggle on if you want tags in both places, or want to match how your SEO audit tool checks pages. Save the settings, and clear any caching plugin if the change doesn't appear to take.
- For a region-specific version of a language, such as French for Canada rather than generic French, go to WPML > Languages > Edit Languages and add a custom language using the code fr-ca. WPML's own documentation is direct about the limit here: it "is not able to warn you if you're using an invalid format," so double-check the code against ISO 639-1 and ISO 3166-1 before saving, the same subtag structure defined in RFC 5646. A typo here produces a tag Google will quietly ignore.
- View the source of a translated page (right-click, View Page Source, or Ctrl+U in most browsers) and search for the word hreflang. You should see one line per language version, plus one marked x-default if you chose to set it. If you see nothing at all, revisit step 1, and if you see tags but they look incomplete, check the sitemap instead, since that may be where the rest live now.
Setting up hreflang with Polylang
Polylang hreflang tags are also automatic, generated in the page head once you've added your languages under Languages > Languages in the dashboard. One genuinely useful thing Polylang does that many plugins skip: it converts WordPress's internal locale format, which uses underscores like en_GB, into the hyphenated form hreflang actually requires, en-GB, matching the subtag format listed in the IANA Language Subtag Registry.
- Confirm your languages are set up under Languages > Languages, with each one showing a flag and a locale. You should see your site's front end offering a language switcher once at least two languages have published content. No switcher usually means no second language has a translated post yet.
- Check Languages > Settings > URL modifications for "Hide URL language information for default language." If this is on, your homepage in the default language sits at the root domain and picks up x-default automatically. This is the one place Polylang applies x-default without extra work; our guide to hreflang x-default covers where that tag should point on other CMS setups too.
- Understand the limit before you go looking for a setting that isn't there: Polylang generates x-default only for the homepage. According to Polylang's own documentation, extending it to inner pages needs a code filter, or Polylang Pro. A related feature request on Polylang's GitHub, asking for this on every page by default, was closed as "wontfix" in 2021, so this isn't a bug you're missing, it's a deliberate design choice.
- View source on a translated page and search for hreflang, the same way you would for WPML. You should see one tag per published language variant of that page. If a language is missing from the list, check whether that specific post has a translation assigned to it; an unlinked translation simply won't appear.
Adding hreflang to WordPress without a plugin
Sometimes a full translation plugin is overkill. If you're running a handful of static pages in two languages, or maintaining entirely separate WordPress installs per market, you can write the tags yourself using WordPress's wp_head hook, the standard action WordPress uses to print content into the page head on the front end. This gives you full control, but it also means nothing updates automatically when you add a page, so it suits small, stable sites better than ones that grow every week.
- Open your child theme's functions.php file (never edit a parent theme directly, since theme updates will erase your changes), or create a small custom plugin file just for this snippet. If you're not sure which file survives a theme update, a must-use plugin in wp-content/mu-plugins/ is the safer choice.
- Add a function hooked to wp_head that prints one
linktag per language version of the current page, using the rel="alternate" relation that hreflang is built on, including a self-referencing tag for the page you're on. Every URL must be absolute (starting with https://), not a relative path like /fr/page, since a relative path leaves search engines unable to resolve which page the tag actually points to.
function ines_output_hreflang_tags() {
$translations = array(
'en' => 'https://example.com/page/',
'fr' => 'https://example.com/fr/page/',
'x-default' => 'https://example.com/page/',
);
foreach ( $translations as $lang => $url ) {
echo '<link rel="alternate" hreflang="' . esc_attr( $lang ) . '" href="' . esc_url( $url ) . '" />' . "\n";
}
}
add_action( 'wp_head', 'ines_output_hreflang_tags' );
- Save the file and reload the page you edited. View the page source and search for hreflang. You should see one tag per language, plus the self-reference, all using hyphens rather than underscores in the language code. If nothing appears, a typo in the hook name is the most common reason a wp_head snippet silently does nothing.
One honest caution here: this method does not update itself. Add a new translated page and forget to update the array, and that page simply has no hreflang tag at all. That's a fine trade for a five-page bilingual site. It's a bad one for anything that grows.
Why your site might be showing two sets of hreflang tags
Unlike missing tags, duplicate tags look like something is working, just working twice. A WordPress.org thread from December 2023 describes exactly this: a single-language UK site somehow had duplicate en, en-GB, and x-default tags on every page, despite the owner not consciously running any multilingual setup at all. The fix came from Health Check's Troubleshooting Mode, deactivating plugins one at a time until the duplicate disappeared.
In WordPress, duplicate hreflang usually traces back to one of four sources. A second translation plugin left active after a migration or a plugin swap. A theme option that also writes SEO tags, buried in a theme's own customizer. A hreflang snippet someone pasted into functions.php years ago and forgot about.
Or, less commonly, a CDN or edge rule adding a Link HTTP header with hreflang values while a plugin separately writes the same information into the HTML head, which produces a mismatch between the two sources rather than a clean duplicate.
One thing worth clearing up before you go hunting for a bug that isn't there: seeing both an hreflang="en" tag and an hreflang="x-default" tag on the exact same URL is normal and expected. That's TranslatePress's documented, intended behavior, and Google's own guidance allows a single URL to serve as both a specific language version and the default fallback. A true duplicate is the same language code appearing twice, like two separate en-GB tags pointing at different URLs. That's the one to chase down.
- View the page source of one translated page and search for hreflang. Count how many lines you see for each language code. One line per code is healthy; two or more for the same code, other than the x-default pairing described above, means you have a genuine duplicate to trace.
- Open yoursite.com/sitemap.xml (or your sitemap plugin's sitemap URL) and check whether hreflang information also appears there, separate from the HTML tags. If WPML is in play, remember that sitemap-based hreflang became the mid-2025 default, so seeing it there while the HTML head is empty is expected, not a bug.
- If you find a real duplicate, use WordPress's built-in Health Check plugin's Troubleshooting Mode to deactivate plugins one by one, reloading the page source after each one, until the second set of tags disappears. You should land on exactly one plugin as the source, at which point you can decide which method to keep and turn the other one off.
Checking your hreflang tags after setup
Reading raw HTML to verify hreflang is tedious and easy to get wrong by eye, especially across several language pairs. Once you've set up WPML, Polylang, or a manual snippet, paste one page and its translated versions into the hreflang.tools checker, which reads HTML tags, HTTP Link headers, and sitemap entries in one pass and tells you plainly what's missing.
A few results you're likely to see on a WordPress site, translated into plain terms: no_hreflang means the plugin isn't outputting tags for that page at all, often an untranslated post. no_x_default is only worth acting on if you actually intended a fallback page; Polylang's homepage-only behavior will trigger this on inner pages by design. duplicate_lang is the real duplicate problem described above. no_return_link means page A points to page B, but B doesn't point back, commonly a draft translation that isn't published yet.
target_noindex and target_redirects show up when a translated page is either blocked from indexing or has moved, both of which TranslatePress is known to include by default unless you exclude those paths in its Advanced Settings. If WPML's sitemap mode is on, check the sitemap format itself rather than expecting HTML tags, or check sitemapcheck.tools's comparison of WordPress sitemap sources to confirm which one WPML is actually writing to.
Frequently Asked Questions
Does Yoast add hreflang tags?
No. Yoast SEO does not generate hreflang tags for any page, confirmed directly by Yoast's own support staff on the WordPress.org forum. Yoast handles canonical tags, sitemaps, and general SEO metadata, but multilingual tagging is outside its feature set. You need a separate translation plugin, such as WPML or Polylang, or a manual code snippet, to get hreflang tags on a WordPress site running Yoast.
How do WPML and Polylang handle hreflang?
Both generate hreflang automatically once you've set up languages, but they differ in detail. WPML writes tags to the sitemap by default since mid-2025, with HTML head tags as an opt-in setting under WPML > Languages > SEO Options. Polylang writes HTML tags automatically and converts WordPress locale codes to the correct hyphenated format, but it only applies x-default to the homepage unless you add a code filter or use Polylang Pro.
Can I add hreflang to WordPress without a plugin?
Yes, using WordPress's wp_head hook in a child theme's functions.php file or a small custom plugin, printing one link tag per language version with absolute URLs and a self-referencing tag. This suits small, static multilingual sites well, since it gives full control over the output. It doesn't scale automatically, though: add a new page and forget to update the code, and that page will have no hreflang tag at all.
Why does WordPress output two sets of hreflang tags?
Usually because more than one source is writing them: a second translation plugin left active after a migration, a theme option that also outputs SEO tags, an old code snippet nobody removed, or a CDN rule adding an HTTP header while a plugin writes the same data into the HTML. Seeing both a specific language tag and an x-default tag on one URL is normal, not a duplicate. Health Check's Troubleshooting Mode, deactivating plugins one at a time, is the most reliable way to find the actual source.