This tutorial will teach you different methods to implement hreflang in WordPress without using a plugin.

Add hreflang without a plugin in a network of websites with predictable URLs

This solution is applicable only if the different versions of your pages have predictable URLs. For example, suppose your website is available in two versions. One is the region-independent English version of the website, and the other is the English version localized for visitors in the United Kingdom.

In a similar scenario, only a limited part of the URL changes, and the part used to identify the website’s specific page remains the same. For example, the different versions of the “Hello World” page may have the following URLs:

  • https://example.com/hello-world/ (region-independent English version)
  • https://example.co.uk/hello-world/ (English version for the United Kingdom)

Consequently, we can easily automate the implementation of hreflang with a script that prints in the page’s head the necessary hreflang tags.

/**
 * Prints the hreflang tag for the English version (region independent) of the website and the English version
 * localized for the United Kingdom.
 */
function apply_hreflang_tag() {

	echo '<link rel="alternate" href="' . esc_url( 'http://example.com' . $_SERVER['REQUEST_URI'] ) . '" hreflang="en" />';
	echo '<link rel="alternate" href="' . esc_url( 'http://example.co.uk' . $_SERVER['REQUEST_URI'] ) . '" hreflang="en-uk" />';

}

//Hook used to print data in the head tag of the front-end
add_action( 'wp_head', 'apply_hreflang_tag' );

In the code above, I adopted wp_head, an action hook generally used to print content in the head section of the page, with my custom apply_hreflang_tag() function.

Add hreflang without a plugin with custom fields

We can create a more versatile script that supports any URL structure using custom fields. More precisely, we will use custom fields to manually define the URL, language, and geo targets of posts, pages, or custom post types.

Create the custom fields

This section will guide you on creating custom fields in your post editor. Specifically, I will make four fields, two for the first version of the page and two for the second version.

/**
 * Adds four custom fields to store the hreflang metadata associated with two different versions of the website.
 */
function add_hreflang_meta_fields( $post_id ) {

	//Add the metadata
	add_post_meta( $post_id, 'hreflang_href_1', '', true );
	add_post_meta( $post_id, 'hreflang_hreflang_1', '', true );
	add_post_meta( $post_id, 'hreflang_href_2', '', true );
	add_post_meta( $post_id, 'hreflang_hreflang_2', '', true );

	return true;

}

add_action( 'wp_insert_post', 'add_hreflang_meta_fields' );

The new fields are now ready to be used. However, the “Custom Fields” section is by default hidden, and you should manually activate it with the editor options.

To enable the “Custom Fields” section, use this procedure:

  1. Click on the three dots on the top-right section of the page.
  2. In the menu, select Preferences.
  3. Click the Panels tab and activate the Custom fields toggle.
  4. Close the modal window.

You should now see the Custom fields section at the bottom of the screen.

Four custom fields used to store hreflang data in the post editor of WordPress.
The four custom fields used to store hreflang data are now available in the post editor.

Add hreflang in the head tag

Thanks to our new custom fields, the WordPress posts now have the necessary hreflang data to properly indicate Google and the other search engine about the localized pages of the website. The next step is to create a script that prints these hreflang data in the page HTML.

The new implementation will again use the wp_head hook to print data in the head tag, but this time the function used to generate the hreflang tags will use the post meta associated with the post.

/**
 * Prints the hreflang tags based on the meta fields value defined in the back-end.
 */
function apply_hreflang_tag() {

	//Get the post meta
	$hreflang_href_1     = get_post_meta( get_the_ID(), 'hreflang_href_1', true );
	$hreflang_hreflang_1 = get_post_meta( get_the_ID(), 'hreflang_hreflang_1', true );
	$hreflang_href_2     = get_post_meta( get_the_ID(), 'hreflang_href_2', true );
	$hreflang_hreflang_2 = get_post_meta( get_the_ID(), 'hreflang_hreflang_2', true );

	//Print the hreflang tags
	echo '<link rel="alternate" href="' . esc_url( $hreflang_href_1 ) . '" hreflang="' . esc_attr( $hreflang_hreflang_1 ) . '" />';
	echo '<link rel="alternate" href="' . esc_url( $hreflang_href_2 ) . '" hreflang="' . esc_attr( $hreflang_hreflang_2 ) . '" />';

}

//Hook used to print data in the head tag of the front-end
add_action( 'wp_head', 'apply_hreflang_tag' );

Note that you can quickly expand this solution to support more than two sites by adding more metadata and printing these data in the head section.

Generate hreflang with an HTTP header

Three methods are available for us to indicate the alternate version of the pages:

  • With hreflang tags in the page HTML
  • In the sitemap XML 
  • With HTTP Headers

In this section, we are going to look at the third option, which technically requires sending a specific HTTP header with the syntax described here.

Suppose you have two “Hello World” pages, one in English and one in Italian. The related hreflang implementation through an HTTP header might be:

Link: <https://example.com/hello-world/>; rel="alternate"; hreflang="en",<https://it.example.com/ciao-mondo/>; rel="alternate"; hreflang="it"

It’s worth noting that applying hreflang with an HTTP header is necessary only with non-HTML pages like pdf documents. However, you are free to use this method with regular HTML pages.

In this case, we will use the send_headers hook to add custom HTTP headers to the page. The script that follows adds hreflang without using a plugin by returning an HTTP header:

/**
 * Add the hreflang data in the HTTP headers.
 */
function add_hreflang_headers() {

	//Get the post ID from the slug
	$post    = get_posts( array(
		'name' => pathinfo( $_SERVER['REQUEST_URI'] )['filename']
	) )[0];
	$post_id = $post->ID;

	//Do not proceed if the post ID is not available
	if ( intval( $post_id, 10 ) === 0 ) {
		return;
	}

	//Get the post meta
	$hreflang_href_1     = get_post_meta( $post_id, 'hreflang_href_1', true );
	$hreflang_hreflang_1 = get_post_meta( $post_id, 'hreflang_hreflang_1', true );
	$hreflang_href_2     = get_post_meta( $post_id, 'hreflang_href_2', true );
	$hreflang_hreflang_2 = get_post_meta( $post_id, 'hreflang_hreflang_2', true );

	//Generate the header
	$header = 'Link:';
	$header .= '<' . $hreflang_href_1 . '>; rel="alternate"; hreflang="' . $hreflang_hreflang_1 . '",';
	$header .= '<' . $hreflang_href_2 . '>; rel="alternate"; hreflang="' . $hreflang_hreflang_2 . '"';

	//Send the HTTP header
	header( $header );

}

add_action( 'send_headers', 'add_hreflang_headers' );

Note that in the code above, the post ID can’t be retrieved using get_the_ID() because this information is still unavailable when the send_headers hook is executed. So I had to get this information with get_posts().

Add hreflang with a plugin

If the implementation of hreflang without a plugin takes too much time or effort and you prefer a ready-to-use solution, consider using the Hreflang Manager plugin.

The free version of this hreflang plugin is distributed on the WordPress.org plugin repository and supports a network of up to ten websites. We also sell a premium version for professional users that includes advanced features like automatic hreflang syncing, import and export functionalities, support of up to 100 websites, hreflang checks, and more.