WordPress International SEO: hreflang Tags, Polylang, and REST API

Implementing international SEO correctly on a multilingual WordPress site requires three things: accurate hreflang annotations on every page, a canonical URL strategy that prevents duplicate-content penalties, and a sitemap that includes all language variants. Polylang (the free version) handles the language management; the code below adds the REST API integration and programmatic hreflang output without relying on premium features.

Problem: A WordPress site serves visitors in multiple languages and countries — hreflang tags are either missing, misconfigured, or generated incorrectly by the multilingual plugin, causing Google to not recognise the alternate language versions.

Solution: Add rel="alternate" hreflang="x-default" for the fallback URL and one rel="alternate" hreflang="lang-REGION" for each locale via wp_head. Every alternate page must also link back to all others — the relationship is symmetric. When using Polylang, verify the generated hreflang output with Google's International Targeting report in Search Console.


The code below outputs correct hreflang link tags using Polylang's API, exposes the language and translation data in the REST API, and generates a language-aware XML sitemap entry.


 using Polylang
add_action( 'wp_head', function () {
    if ( ! function_exists( 'pll_the_languages' ) ) {
        return;
    }
    // Get all translations for the current post/page
    $translations = pll_the_languages( [
        'raw'           => true,
        'hide_if_empty' => false,
    ] );

    if ( empty( $translations ) ) {
        return;
    }

    foreach ( $translations as $lang ) {
        if ( ! empty( $lang['url'] ) ) {
            printf(
                '' . "\n",
                esc_attr( $lang['locale'] ),
                esc_url( $lang['url'] )
            );
        }
    }

    // x-default points to the default language URL
    $default_lang = pll_default_language( 'locale' );
    if ( isset( $translations[ $default_lang ]['url'] ) ) {
        printf(
            '' . "\n",
            esc_url( $translations[ $default_lang ]['url'] )
        );
    }
}, 5 );

// 2. Expose language and translations in the REST API
add_action( 'rest_api_init', function () {
    register_rest_field( 'post', 'language', [
        'get_callback' => function ( array $post ) {
            return function_exists( 'pll_get_post_language' )
                ? pll_get_post_language( $post['id'], 'locale' )
                : null;
        },
        'schema' => [ 'type' => 'string', 'context' => [ 'view', 'edit' ] ],
    ] );

    register_rest_field( 'post', 'translations', [
        'get_callback' => function ( array $post ) {
            if ( ! function_exists( 'pll_get_post_translations' ) ) {
                return null;
            }
            $raw = pll_get_post_translations( $post['id'] );
            $out = [];
            foreach ( $raw as $lang => $pid ) {
                $out[ $lang ] = [
                    'id'  => $pid,
                    'url' => get_permalink( $pid ),
                ];
            }
            return $out;
        },
        'schema' => [ 'type' => 'object', 'context' => [ 'view', 'edit' ] ],
    ] );
} );


NOTE: Google only follows hreflang annotations if they are reciprocal — every language variant must link to all others, and each must link back; if a translation's hreflang tag is missing or incorrect, Google will ignore the entire hreflang cluster for that page and may serve the wrong language version in search results.