🏪 Multi-boutiqueAvancé PS 1.6 PS 1.7 PS 8.x

Multiboutique PrestaShop : afficher les produits d'un autre site avec Homefeatured

Comment afficher les produits d'une boutique B sur la page d'accueil d'une boutique A en multiboutique PrestaShop. Guide complet avec code Smarty et SQL.

En bref : Pour afficher les produits d'une boutique B sur la page d'accueil d'une boutique A en multiboutique PrestaShop, la solution recommandée est un module personnalisé avec requête SQL ciblant explicitement l'id_shop source, combiné à une génération d'URLs via Shop::setContext() pour pointer vers le bon domaine.

Publié le 21 mars 2026 8 min de lecture Alexandre Carette

Le défi du cross-site en multiboutique PrestaShop

Le mode multiboutique de PrestaShop permet de gérer plusieurs sites e-commerce depuis un seul back-office. Mais une limitation revient régulièrement : comment afficher sur la boutique A les produits qui appartiennent à la boutique B ?

Par défaut, le module Homefeatured (remplacé par ps_featuredproducts depuis PrestaShop 1.7) ne récupère que les produits associés à la boutique courante. Les requêtes SQL internes filtrent systématiquement par id_shop, ce qui empêche tout affichage cross-site natif.

Ce guide détaille deux approches pour contourner cette limitation, de la plus simple à la plus robuste.

Comprendre le filtrage multiboutique dans PrestaShop

Avant de modifier quoi que ce soit, il faut comprendre comment PrestaShop isole les données par boutique.

Le mécanisme Shop::addSqlRestriction

Chaque requête SQL générée par PrestaShop passe par la méthode Shop::addSqlRestriction(). Cette méthode ajoute automatiquement une clause WHERE qui filtre par id_shop ou par id_shop_group selon le contexte.

Dans le module Homefeatured (PS 1.6) ou ps_featuredproducts (PS 1.7+), la requête de récupération des produits ressemble à :


SELECT p.id_product, pl.name, pl.link_rewrite, i.id_image
FROM ps_product p
INNER JOIN ps_product_shop ps ON (ps.id_product = p.id_product AND ps.id_shop = {id_shop_courante})
INNER JOIN ps_product_lang pl ON (pl.id_product = p.id_product AND pl.id_shop = {id_shop_courante})
WHERE p.active = 1

Le filtre ps.id_shop = {id_shop_courante} est la raison pour laquelle les produits de la boutique B n'apparaissent jamais sur la boutique A.

Approche 1 : partager les produits entre boutiques (la plus simple)

La méthode la plus directe consiste à associer les produits aux deux boutiques depuis le back-office, puis à personnaliser le template pour rediriger les liens vers le bon domaine.

Étape 1 — Associer les produits aux deux boutiques

Depuis le back-office PrestaShop :

  1. Passez en contexte **"Toutes les boutiques"** via le sélecteur multiboutique
  2. Ouvrez chaque produit concerné
  3. Dans l'onglet **Associations**, cochez les deux boutiques
  4. Sauvegardez
  5. Pour une association en masse, utilisez cette requête SQL (à adapter) :

    
    -- Associer tous les produits de la boutique 2 à la boutique 1
    INSERT IGNORE INTO ps_product_shop (id_product, id_shop, id_category_default, id_tax_rules_group, 
      on_sale, online_only, ecotax, minimal_quantity, price, wholesale_price, unity, unit_price_ratio,
      additional_shipping_cost, customizable, uploadable_files, text_fields, active, redirect_type,
      available_for_order, available_date, show_condition, condition, show_price, indexed, visibility,
      cache_default_attribute, advanced_stock_management, date_add, date_upd, pack_stock_type)
    SELECT id_product, 1, id_category_default, id_tax_rules_group,
      on_sale, online_only, ecotax, minimal_quantity, price, wholesale_price, unity, unit_price_ratio,
      additional_shipping_cost, customizable, uploadable_files, text_fields, active, redirect_type,
      available_for_order, available_date, show_condition, condition, show_price, indexed, visibility,
      cache_default_attribute, advanced_stock_management, date_add, date_upd, pack_stock_type
    FROM ps_product_shop
    WHERE id_shop = 2;
    

    Attention : n'oubliez pas de dupliquer également les entrées dans ps_product_lang pour la boutique cible, sinon les noms et descriptions seront vides.

    Étape 2 — Personnaliser le template pour les liens cross-site

    Une fois les produits visibles sur les deux boutiques, le problème suivant est que les liens pointent vers le domaine de la boutique courante au lieu de la boutique d'origine.

    Pour corriger cela, il faut surcharger le template de la liste produits pour réécrire les URLs.

    #### Sur PrestaShop 1.6 (Smarty)

    Créez un fichier home-product-list.tpl dans votre thème :

    
    {foreach from=$products item=product name=homeProducts}
    <div class="product-container">
        <div class="product-image-container">
            {* Déterminer le domaine cible selon la boutique d'origine du produit *}
            {assign var='target_domain' value='https://www.boutique-b.com'}
            
            <a class="product_img_link" 
               href="{$target_domain}/{$product.link_rewrite|escape:'html':'UTF-8'}-{$product.id_product}.html" 
               title="{$product.name|escape:'html':'UTF-8'}">
                <img class="img-responsive" 
                     src="{$link->getImageLink($product.link_rewrite, $product.id_image, 'home_default')}" 
                     alt="{$product.name|escape:'html':'UTF-8'}" />
            </a>
        </div>
        <div class="product-meta">
            <h5>
                <a href="{$target_domain}/{$product.link_rewrite|escape:'html':'UTF-8'}-{$product.id_product}.html">
                    {$product.name|truncate:45:'...'|escape:'html':'UTF-8'}
                </a>
            </h5>
            <span class="price">{$product.price|escape:'html':'UTF-8'}</span>
        </div>
    </div>
    {/foreach}
    

    Puis dans homefeatured.tpl, remplacez l'appel au template standard :

    
    {if isset($products) && $products}
        {include file="$tpl_dir./home-product-list.tpl" class='homefeatured tab-pane' id='homefeatured'}
    {else}
        <ul id="homefeatured" class="homefeatured tab-pane">
            <li class="alert alert-info">{l s='No featured products at this time.' mod='homefeatured'}</li>
        </ul>
    {/if}
    

    #### Sur PrestaShop 1.7+ / 8.x

    Le module ps_featuredproducts utilise un système de templates différent. Surchargez le template dans votre thème :

    
    themes/votre-theme/modules/ps_featuredproducts/views/templates/hook/ps_featuredproducts.tpl
    

    La logique reste identique : intercepter le lien produit et remplacer le domaine.

    Limites de cette approche

    • **Maintenance double** : chaque modification de produit doit être répétée ou synchronisée
    • **URLs en dur** : le domaine cible est codé dans le template
    • **Stocks partagés** : le stock est commun aux deux boutiques (sauf configuration avancée)

    Approche 2 : requête SQL cross-shop dans un module personnalisé (recommandé)

    Pour une solution plus propre et maintenable, la meilleure approche consiste à créer un module qui effectue directement une requête cross-shop.

    Le principe

    Au lieu de laisser PrestaShop filtrer par boutique, on écrit une requête qui cible explicitement la boutique source :

    
    <?php
    /**
     * Module d'affichage cross-boutique pour la page d'accueil
     * Compatible PrestaShop 1.7 / 8.x
     */
    class AcCrossShopFeatured extends Module
    {
        public function __construct()
        {
            $this->name = 'ac_crossshopfeatured';
            $this->version = '1.0.0';
            $this->author = 'Alexandre Carette';
            parent::__construct();
            $this->displayName = $this->l('Cross-Shop Featured Products');
        }
    
        public function hookDisplayHome($params)
        {
            $targetShopId = (int) Configuration::get('AC_CROSSSHOP_TARGET_ID');
            $categoryId = (int) Configuration::get('AC_CROSSSHOP_CATEGORY_ID');
            $limit = (int) Configuration::get('AC_CROSSSHOP_LIMIT') ?: 8;
            $idLang = (int) $this->context->language->id;
    
            $sql = new DbQuery();
            $sql->select('p.id_product, pl.name, pl.link_rewrite, pl.description_short');
            $sql->select('ps.price, i.id_image');
            $sql->from('product', 'p');
            $sql->innerJoin('product_shop', 'ps', 'ps.id_product = p.id_product AND ps.id_shop = ' . $targetShopId);
            $sql->innerJoin('product_lang', 'pl', 'pl.id_product = p.id_product AND pl.id_lang = ' . $idLang . ' AND pl.id_shop = ' . $targetShopId);
            $sql->leftJoin('image_shop', 'i', 'i.id_product = p.id_product AND i.id_shop = ' . $targetShopId . ' AND i.cover = 1');
            
            if ($categoryId) {
                $sql->innerJoin('category_product', 'cp', 'cp.id_product = p.id_product AND cp.id_category = ' . $categoryId);
            }
    
            $sql->where('ps.active = 1');
            $sql->where('ps.visibility IN ("both", "catalog")');
            $sql->orderBy('p.date_add DESC');
            $sql->limit($limit);
    
            $products = Db::getInstance()->executeS($sql);
    
            // Récupérer le domaine de la boutique cible
            $targetShopUrl = $this->getShopUrl($targetShopId);
    
            $this->context->smarty->assign([
                'crossProducts' => $products,
                'targetShopUrl' => $targetShopUrl,
            ]);
    
            return $this->display(__FILE__, 'views/templates/hook/crossshop-featured.tpl');
        }
    
        private function getShopUrl(int $shopId): string
        {
            $shop = new Shop($shopId);
            $ssl = Configuration::get('PS_SSL_ENABLED') ? 'https://' : 'http://';
            return $ssl . $shop->domain_ssl;
        }
    }
    

    Le template associé

    
    {if $crossProducts && count($crossProducts) > 0}
    <section class="featured-products cross-shop-products">
        <h2>{l s='Sélection de notre boutique partenaire' mod='ac_crossshopfeatured'}</h2>
        <div class="products-grid">
            {foreach from=$crossProducts item=product}
            <article class="product-miniature">
                <a href="{$targetShopUrl}/{$product.link_rewrite}-{$product.id_product}.html">
                    <img src="{$link->getImageLink($product.link_rewrite, $product.id_image, 'home_default')}" 
                         alt="{$product.name|escape:'html':'UTF-8'}" loading="lazy" />
                </a>
                <h3>
                    <a href="{$targetShopUrl}/{$product.link_rewrite}-{$product.id_product}.html">
                        {$product.name|escape:'html':'UTF-8'}
                    </a>
                </h3>
                <span class="price">{Tools::displayPrice($product.price)}</span>
            </article>
            {/foreach}
        </div>
    </section>
    {/if}
    

    Construction correcte des URLs cross-boutique

    L'un des pièges majeurs du cross-site multiboutique est la génération des URLs produit. Par défaut, $link->getProductLink() génère toujours une URL relative à la boutique courante.

    Le problème

    Si vous êtes sur boutique-a.com et affichez un produit de boutique-b.com, l'appel standard :

    
    $link->getProductLink($product);
    

    …retournera https://boutique-a.com/produit-1.html au lieu de https://boutique-b.com/produit-1.html.

    La solution propre en PHP

    Utilisez le contexte de la boutique cible temporairement :

    
    // Sauvegarder le contexte actuel
    $currentShopId = Shop::getContextShopID();
    
    // Basculer vers la boutique cible
    Shop::setContext(Shop::CONTEXT_SHOP, $targetShopId);
    $targetLink = new Link();
    $productUrl = $targetLink->getProductLink(
        $product['id_product'],
        $product['link_rewrite'],
        null,
        null,
        $idLang,
        $targetShopId
    );
    
    // Restaurer le contexte original
    Shop::setContext(Shop::CONTEXT_SHOP, $currentShopId);
    

    Cette méthode est la seule qui respecte les règles de réécriture d'URL configurées pour chaque boutique (URL simplifiée ou non, préfixe de langue, etc.).

    Gestion des images en cross-boutique

    Les images produit sont un autre point de vigilance. En multiboutique, les images sont partagées physiquement sur le serveur mais leur association est filtrée par id_shop dans la table ps_image_shop.

    Si vous avez associé vos produits aux deux boutiques (approche 1), les images sont déjà accessibles. Sinon, vous devrez construire manuellement l'URL de l'image :

    
    $imageUrl = _PS_BASE_URL_ . '/img/p/' 
        . implode('/', str_split((string) $idImage)) 
        . '/' . $idImage . '-home_default.jpg';
    

    Ou plus proprement via le Link object :

    
    $imageUrl = $link->getImageLink($product['link_rewrite'], $product['id_image'], 'home_default');
    

    Bonnes pratiques pour le multiboutique cross-site

    Performance

    • **Cachez les résultats** : les requêtes cross-shop ajoutent des jointures supplémentaires. Utilisez le cache PrestaShop (`Cache::store()`) avec un TTL de 1 heure minimum
    • **Indexez correctement** : vérifiez que `ps_product_shop` a bien un index composite sur `(id_product, id_shop)`

    SEO

    • **Canonical URLs** : assurez-vous que chaque produit a une URL canonique qui pointe vers sa boutique principale pour éviter le contenu dupliqué
    • **Attribut `rel="nofollow"`** : sur les liens cross-site de la page d'accueil, envisagez d'ajouter `rel="nofollow"` si vous voulez que le jus SEO reste sur chaque domaine respectif

    Maintenance

    • **Documentez la configuration** : notez quelle boutique est la source et quelle est la cible
    • **Testez les deux sens** : vérifiez les URLs générées depuis chaque boutique
    • **Surveillez les 404** : après une mise à jour de produit, vérifiez que les liens cross-site restent valides

    Conclusion

    L'affichage cross-boutique dans PrestaShop multiboutique n'est pas supporté nativement, mais reste tout à fait réalisable. L'approche la plus maintenable est le module personnalisé avec requête SQL ciblée, car elle offre un contrôle total sur les données affichées et les URLs générées. Pour des besoins ponctuels, le partage de produits avec surcharge de template reste une solution rapide et fonctionnelle.

#multiboutique #homefeatured #smarty #cross-selling #produits #multisite

Questions fréquentes

Tout ce que vous devez savoir sur ce sujet.

Un projet PrestaShop ?

Discutons-en directement.

★★★★★

193 projets livrés

Gratuit & sans engagement — réponse sous 24h

Alexandre Carette

Alexandre Carette

Expert PrestaShop & Architecture E-commerce

Développeur PrestaShop depuis 2014, 193 projets livrés. Je conçois des architectures headless Nuxt + PrestaShop et des outils d'automatisation IA pour les e-commerçants.