Magento by default stores related, upsell and cross sell products in general scope. Is there an extension to store them at store scope? Does anyone have experience implementing this feature?

2 Answers 2


The saving of the relation is done in the Mage_Adminhtml_Catalog_ProductController. You can trace it via the saveAction method to the _initProductSave method where around line 640 it's set in the product object.

In the Mage_Catalog_Model_Product class, method _afterSave on line 540 the method Mage_Catalog_Model_Product_Link::saveProductRelations is called that calls Mage_Catalog_Model_Resource_Product_Link::saveProductLinks with the argument $product, $data and $typeId that is actually persisted into the database which happens around line 99.

Now, with all that back tracing we can actually do something to solve your question.

To do this properly write your own module, a tutorial about that you can find here on Tutsplus.com.

You'll also need an install script. Check out this Inchoo tutorial about that.

So, first let's add a column to the catalog_product_link table the data is actually stored in (thank you @MagePsycho for that diagram)

$installer = $this;
$connection = $installer->getConnection();


        'type' => Varien_Db_Ddl_Table::TYPE_INT,
        'nullable' => false,
        'default' => 0,
        'comment' => 'Store ID'


Now we'll rewrite the resource model actually saving the data to also include the store ID.

Add the following to your modules config.xml, in the global node.


with the following contents

class [Namespace]_[Module]_Model_Resource_Catalog_Product_Link extends Mage_Catalog_Model_Resource_Product_Link

     * Save Product Links process
     * @param Mage_Catalog_Model_Product $product
     * @param array $data
     * @param int $typeId
     * @return Mage_Catalog_Model_Resource_Product_Link
    public function saveProductLinks($product, $data, $typeId)
        if (!is_array($data)) {
            $data = array();

        $attributes = $this->getAttributesByType($typeId);
        $adapter    = $this->_getWriteAdapter();

        $bind   = array(
            ':product_id'    => (int)$product->getId(),
            ':link_type_id'  => (int)$typeId
        $select = $adapter->select()
            ->from($this->getMainTable(), array('linked_product_id', 'link_id'))
            ->where('product_id = :product_id')
            ->where('link_type_id = :link_type_id');

        $links   = $adapter->fetchPairs($select, $bind);

        $deleteIds = array();
        foreach($links as $linkedProductId => $linkId) {
            if (!isset($data[$linkedProductId])) {
                $deleteIds[] = (int)$linkId;
        if (!empty($deleteIds)) {
            $adapter->delete($this->getMainTable(), array(
                'link_id IN (?)' => $deleteIds,

        foreach ($data as $linkedProductId => $linkInfo) {
            $linkId = null;
            if (isset($links[$linkedProductId])) {
                $linkId = $links[$linkedProductId];
            } else {

                 * The actual change in the code

                $storeId = (int)$this->getRequest()->getParam('store');

                $bind = array(
                    'product_id'        => $product->getId(),
                    'linked_product_id' => $linkedProductId,
                    'link_type_id'      => $typeId,
                    'store_id'          => $storeId
                $adapter->insert($this->getMainTable(), $bind);
                $linkId = $adapter->lastInsertId($this->getMainTable());

            foreach ($attributes as $attributeInfo) {
                $attributeTable = $this->getAttributeTypeTable($attributeInfo['type']);
                if ($attributeTable) {
                    if (isset($linkInfo[$attributeInfo['code']])) {
                        $value = $this->_prepareAttributeValue($attributeInfo['type'],
                        $bind = array(
                            'product_link_attribute_id' => $attributeInfo['id'],
                            'link_id'                   => $linkId,
                            'value'                     => $value
                        $adapter->insertOnDuplicate($attributeTable, $bind, array('value'));
                    } else {
                        $adapter->delete($attributeTable, array(
                            'link_id = ?'                   => $linkId,
                            'product_link_attribute_id = ?' => $attributeInfo['id']

        return $this;

I'll be honest, it's not to 'clean' to retrieve post data in the resource model but this way you don't need to rewrite all above classes and methods to accept an extra $storeId variable.

Now for retrieving the right collection in the grid when editing the product. In that case you'll need to rewrite the following blocks

  • Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Related
  • Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Upsell
  • Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Crosssell

and change the method _prepareCollection to add a store ID filter to the retrieved collection around line 99. Which would look something like this

$collection = Mage::getModel('catalog/product_link')->use[...]Links()
            ->addAttributeToFilter('store_id', $this->getRequest()->getParam('store_id'));

DISCLAIMER: this is untested code. Which should work but might need some tweaking here and there.


As from the table structure for linked products (related, upsell & cross sell), it's clear that there is no store_id. enter image description here

So first step will be adding a store_id field to the table. Then you need to prepare the the linked product ids while displaying and saving the products as per store view.

May need lots of tweaking, just a rough idea to get started.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.