Implement rel alternate links in Magento

rel alternate featured image

With the rise of E-commerce, selling goods to customers around the globe became easier than ever before. Internet has no borders, and anyone can ‘walk into your store’ whilst being in comfort of his own home.

An increasing need for multilingual stores has appeared. You might want your customers to be able to browse your store in their own language.

When you’re setting up a multilingual store, Google has some directives and recommendations as to what should be done for your website to be indexed correctly, and rank higher in their search.

One of those recommendations is implementation of rel=”alternate” links, if you have pages translated to another language.

Syntax we should be using looks like this:

<link rel="alternate" hreflang="es" href="" />

If we adapt this syntax to a Magento store, this would look like:

<link rel="alternate" hreflang="first-two-letters-of-a-store-locale" href="url-of-the-page-in-other-language" />

Now that we have an idea what we should do, let’s get started by registering our module.


<?xml version="1.0"?>

Let’s talk our module’s configuration file a bit. What we need to do is insert alternate links in every pages’ head. One way of doing this would be creating logic needed in head.phtml file.

The better way would be using an observer to listen to some event, and insert links to head using a method Magento already has – addLinkRel().

In this example, we’ll use controller_action_layout_generate_blocks_after event. There probably is some other event that is more suitable, but this one will do the trick.


<?xml version="1.0"?>

We just defined what will be the name our observer and it’s method, let’s create them now.


class Inchoo_Alternate_Model_Observer
 public function alternateLinks()
   $headBlock = Mage::app()->getLayout()->getBlock('head');
   $stores = Mage::app()->getStores();
   $prod = Mage::registry('current_product');
   $categ = Mage::registry('current_category');
     foreach ($stores as $store)
         $categ ? $categId=$categ->getId() : $categId = null;
         $url = $store->getBaseUrl() . Mage::helper('inchoo_alternate')->rewrittenProductUrl($prod->getId(), $categId, $store->getId());
       $storeCode = substr(Mage::getStoreConfig('general/locale/code', $store->getId()),0,2);
       $headBlock->addLinkRel('alternate"' . ' hreflang="' . $storeCode, $url);
   return $this;

As you can see, we’re looping through all the stores. If a product is set, we’ll check if it’s URL is rewritten. That’s what our helper will be for. If a product isn’t set, with getCurrentUrl() method, we’ll get current URL in different store.

E.g. if our current url is, in our German store, it will be or even, depending on your Magento configuration.

Next up, we’ll get first two letters our store’s locale code. This might not be perfect if you have e.g. two english stores, one for Great Britain, and one for US, but I’ll let you figure this one out.

After we have gotten all the information we needed, we’ll call the head block and use it’s addLinkRel() method to add the links to the head.

Now, create your helper

class Inchoo_Alternate_Helper_Data extends Mage_Core_Helper_Abstract
    public function rewrittenProductUrl($productId, $categoryId, $storeId)
        $coreUrl = Mage::getModel('core/url_rewrite');
        $idPath = sprintf('product/%d', $productId);
        if ($categoryId) {
            $idPath = sprintf('%s/%d', $idPath, $categoryId);
        return $coreUrl->getRequestPath();

Read the article here for an explanation on how the helper’s method works.

That’s pretty much it. Inspect your website to see the links 🙂

This article was updated on 29th of May


  1. please do not use this code, it doesn’t work correctly OR please update your code to have something correct.
    $stores = Mage::app()->getStores();
    It takes all stores and not only store of the current website.
    So if you have multiwebsite configuration, this scipt will generate alternate link with all your other domains.
    Probably need to replace by something by : $stores = Mage::getModel(‘core/website’)->load($websiteId)->getStoreIds();

  2. Hi,
    This extension is working fine but hreflang tag is not coming up on login/register page. Anyone can help me?
    I will really appreciate it.


  3. Hi there, I’ve implemented this module a while ago, but I found a problem with it. When it’s adding the rel=alternate links, my rel=canonical seems to disappear. Any idea what is happening?

    1. Yes, I do! 😉
      The function addItem that is called by addLinkRel has a key composed by Item type (in our case “link_rel”) and href. That are exactly the same as the canonical… So the Inchoo module overwrite the canonical tag for current url.

    2. Hi Marvin, Manuel,
      Did you find a solution to keep the rel=canonical and add the rel=alternate in the headblock ?

  4. This add ‘hreflang’ tag only in product pages but not in homepage and category pages. Do you notice of this?

    Anyway, thank you.

  5. Why go through all of this… why not add this in configuration > general > design > html head > miscellaneous scripts …

    1. …because every single page needs to be linked with all specific alternate pages, not just the starting page of the whole shop. If you have product A in 10 different stores, google wants to know where to find product A in every single shop. Same about product B, Google wants to know all urls and language codes where product B can be found. By using your method, you could only set up one set of alternate urls (starting page?!) which will be embedded in every page. This is wrong, I did this first and checked Google webmastertools … guess what? Google complained about wrong hreflang setup 😀

      But what you can use this field for is the x-default link, because this is always the same if you have one single page where you can choose in between all existing shops.

    1. Line #19 in Observer.php, I forgot to mention!

      I has a few errors, but hey, it’s a good start point. Thanks a lot !

  6. On the Observer.php file, you have a typo at line 9:
    $headblock should be $headBlock .

  7. Hello, friend a question related to events, I would have a observe to find out when the home page runs, which event should I use? I’m new to the world of magento thanks

  8. Hi, Elvis.

    You sure had lots of problems with this module 🙂
    First thing first, this module works for multiple store views, but I haven’t had a need to implement it for multiple websites.

    As far as rewrites go, it can get pretty complicated to get a rewritten url of a product in different store. You’d have to parse current URL in default store, extract only it’s path, and remove the trailing slash on the front. After that, compare it against core_url_rewrite table by passing the path to core/url_rewrite model’s method loadByRequestPath(). You’d also need to skip system rewrites. Thing is, before you do anything, you’d also have to check if your current url is already a rewritten url. Have fun doing that 😀

    As for the third issue you’re having, you should implement a check for disabled store views in the observer code) .

    This shouldn’t be hard. Keep in mind that this works for simpler Magento stores, but you’ll, of course, need to modify the code to suit your specific needs 🙂

  9. <link rel="alternate" hreflang="en" href="" />
    <link rel="alternate" hreflang="en" href="" />
    <link rel="alternate" hreflang="hr" href="" />
    <link rel="alternate" hreflang="en" href="" />
    <link rel="alternate" hreflang="en" href="" />
    <link rel="alternate" hreflang="sl" href="" />
    <link rel="alternate" hreflang="bs" href="" />
    <link rel="alternate" hreflang="it" href="" />
    <link rel="alternate" hreflang="sr" href="" />
  10. Hi… I did not enabled the module sorry…

    However your module has 2 major errors which lead to more trouble in Google if it is used.

    I will show you the otput of the module:

    1. There is 3 times EN as language code since I have 3 different stores on english but for 3 different countries. It should use 5 letter language code.

    2. More important and harder for me to fix it is that PRODUCT url on one store view is not the same as product url in other so you alternate url’s are not generated from the database and config of each store view but simple as copy paste from current one.

  11. Hi, I had install your extension and see nothing on the frontend. I dont have store name with multiple store views maybe that is the reason.

    But I have multiple stores in website each with single store view. This need to be acting similar right?

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <blockquote cite=""> <code> <del datetime=""> <em> <s> <strike> <strong>. You may use following syntax for source code: <pre><code>$current = "Inchoo";</code></pre>.