Table des matières :
- Archiver un produit PrestaShop : ce que le cœur sait faire (et ce qu’il ne fait pas)
- Spécification d’un module “Archive Products” viable en production
- Modélisation des données : colonne SQL vs table dédiée (et ce que ça change)
- Implémentation Back-Office PrestaShop 8.1 / 9.1 : grilles, formulaires, actions de masse
- Front-Office : exclure du catalogue sans casser l’historique, les caches et la recherche
- Performance, volumétrie et maintenance : archiver n’est pas “gratuit”
- Déploiement, sécurité et qualité : éviter le module “qui marche jusqu’au prochain upgrade”
Archiver un produit PrestaShop : ce que le cœur sait faire (et ce qu’il ne fait pas)
Dans PrestaShop (8.1.x et 9.1.x), le cœur ne connaît pas la notion d’« archivage » au sens métier. Vous avez essentiellement trois leviers natifs : active (on/off), visibility (both, catalog, search, none) et quelques drapeaux comme available_for_order. En pratique, beaucoup d’équipes « archivent » en désactivant (active = 0) ou en mettant visibility = none, mais ce sont des contournements : rien ne structure le cycle de vie (raison, date, auteur, remplacement), et rien ne garantit une cohérence sur tous les points d’entrée (grilles BO, recherche, flux, API, indexation, caches).
La suppression, elle, est presque toujours une mauvaise idée sur un shop en production : vous cassez la traçabilité (lignes de commande, historiques de prix, retours, échanges ERP), vous multipliez les orphelins (tables de liaison catégories, images, spécifiques) et vous transformez la maintenance en chasse aux effets de bord. C’est précisément l’intérêt d’un module PrestaShop Archive Products : organiser un « soft delete » contrôlé, sans toucher aux données historiques.
Un exemple très concret (vécu sur des catalogues « longue traîne ») : un produit n’est plus vendu, mais il apparaît encore dans des emails transactionnels, des tickets SAV, des pages “commande” côté compte client, ou des exports comptables. Si vous supprimez la fiche, vous perdez le contexte (nom, caractéristiques, référence), et votre support bascule sur des reconstitutions manuelles. À l’inverse, l’archivage conserve la donnée, tout en évitant la pollution du catalogue actif.
Côté SEO et UX, archiver ne veut pas dire la même chose selon votre contexte :
- si le produit doit réellement disparaître, un statut HTTP adapté (410/404) + retrait sitemap est cohérent ;
- si le produit n’est plus vendable mais reste utile (modèle remplacé, fiche technique, compatibilités), il faut un mode « archive SEO » : page accessible, non listée, pas indexée (ou redirigée), et message clair.
Sur le plan protocolaire, rappelez-vous ce que dit la RFC HTTP : « The 410 (Gone) status code indicates that access to the target resource is no longer available at the origin server and that this condition is likely to be permanent. » (section 15.5.11 — RFC 9110). C’est une arme utile pour purge d’index, mais destructrice côté acquisition si vous ne gérez pas les remplacements.
Dernier point « terrain » : en multi-boutique, la notion d’archive est rarement globale. Un produit peut être retiré d’une boutique B2C (fin de collection) tout en restant visible et commandable en B2B (stock résiduel, conditions spécifiques). Sans gestion explicite du multi-shop, vous recréez des exceptions et des manipulations manuelles.
Spécification d’un module “Archive Products” viable en production
Un module d’archivage sérieux commence par définir ce qu’est “archivé” dans votre shop, et ce que ça implique. Dans la plupart des catalogues, l’archivage doit gérer au minimum : la date d’archivage, l’auteur (employé), une raison (fin de série, saisonnier, contrainte légale), et un « produit de remplacement » (id produit cible ou URL). Sans ces métadonnées, vous ne faites que cacher des produits, et vous perdez l’information opérationnelle.
Deux modes d’archivage reviennent souvent :
- Archive « hors ligne » :
active=0+ éventuellement redirection/410. Avantage : le cœur exclut naturellement des requêtes front (listing, search) ; inconvénient : la fiche n’est plus accessible (selon thème/contrôleur), donc vous perdez un point d’entrée SEO. - Archive « SEO » :
active=1+visibility=none+available_for_order=0+ gestion explicite FO (bandeau, noindex, redirection vers remplaçant). Avantage : vous gardez la page pour les liens externes, la doc produit, les SAV ; inconvénient : vous devez garantir l’exclusion des moteurs internes (recherche, cross-sell, accessoires, flux).
Pour éviter les “archivages bricolés”, formalisez des règles de sortie du catalogue. Une checklist simple (et testable) pour une spec d’archivage produit PrestaShop :
- Règles de visibilité : le produit archivé doit-il rester accessible via son URL directe ? doit-il être exclu des catégories/recherche ?
- Règles d’achat : blocage panier/commande (y compris via API si vous avez des commandes externes).
- Règles SEO : 301 vers remplaçant, 200+noindex, 410 ; cohérence sitemap ; cohérence canonicals.
- Règles de substitution : gestion d’un “remplaçant” (et comportement si le remplaçant est lui‑même archivé).
- Règles de flux : exclusion de Google Merchant / marketplaces / exports CSV / webservice.
- Règles multi-shop : archivé par boutique (id_shop) + UX BO claire pour ne pas se tromper de contexte.
- Traçabilité : qui, quand, pourquoi ; et réversibilité (désarchiver) avec journal.
Côté Back-Office, attendez-vous à implémenter :
- une colonne/filtre “Archivé” dans la grille produits ;
- une action de masse “Archiver / Désarchiver” ;
- une protection anti-réactivation accidentelle (droit dédié, confirmation + log).
Pour faire ça proprement en 8/9, l’implémentation passe par les hooks des grilles Symfony (voir plus bas) plutôt que des overrides de contrôleurs.
Enfin, si vous exposez votre catalogue via API (ERP/PIM/marketplaces), un statut “archived” doit aussi être disponible côté webservice ou API custom. Sinon, vos flux vont republier des produits “archivés”, et vous reviendrez au point de départ.
Deux cas “catalogue réel” à anticiper dès la spec (sinon vous les découvrirez en prod) :
- Produits pack / bundles : si un composant est archivé, que fait-on du pack ? (interdire commande, remplacer, ou garder vendable si stock/approvisionnement ?)
- Déclinaisons : archiver le produit parent vs archiver certaines combinaisons (taille/couleur) n’est pas la même mécanique. Si votre besoin est “archiver une variation”, un module “Archive Products” doit l’indiquer clairement (hors scope ou gestion dédiée).
Modélisation des données : colonne SQL vs table dédiée (et ce que ça change)
Le choix du stockage conditionne la maintenabilité. Ajouter une colonne archived dans ps_product ou ps_product_shop est tentant, mais ça implique :
- migration SQL sur une table cœur (risque sur upgrades, conflits avec d’autres modules) ;
- gestion multi-boutique délicate (un produit peut être archivé dans une boutique et actif dans une autre →
ps_product_shopest plus juste queps_product).
La voie la plus robuste côté module est une table dédiée, par exemple :
CREATE TABLE IF NOT EXISTS ps_product_archive (
id_product INT UNSIGNED NOT NULL,
id_shop INT UNSIGNED NOT NULL,
archived TINYINT(1) NOT NULL DEFAULT 0,
archived_at DATETIME NULL,
archived_by INT UNSIGNED NULL,
reason VARCHAR(255) NULL,
replacement_id_product INT UNSIGNED NULL,
PRIMARY KEY (id_product, id_shop),
KEY idx_archived (id_shop, archived)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Cette modélisation évite de toucher aux schémas cœur, respecte le multi-shop, et vous permet de stocker des métadonnées sans bricoler des champs existants.
Pour clarifier l’impact, voici un comparatif rapide (utile quand vous devez arbitrer entre “rapide” et “pérenne”) :
| Option | Avantages | Risques / coûts |
|---|---|---|
Colonne dans ps_product |
Requêtes simples, pas de join | Inadapté au multi-shop, touche une table cœur, migrations plus risquées |
Colonne dans ps_product_shop |
Multi-shop cohérent, requêtes simples | Touche une table cœur, collisions possibles avec d’autres évolutions |
Table dédiée ps_product_archive |
Upgrade-safe, extensible (raison, auteur, date, remplaçant), multi-shop natif | Joins à gérer, discipline d’indexation requise |
Le revers : vous devez join cette table dans les grilles et dans certains points du front. Mais ce coût est maîtrisable si vous indexez correctement ((id_shop, archived)), et si vous n’ajoutez pas de jointures inutiles sur des pages ultra-fréquentées. Pour dimensionner, vous pouvez réutiliser les réflexes d’optimisation décrits dans l’article interne sur les index MySQL et EXPLAIN : article interne sur les index MySQL et EXPLAIN
Un point souvent oublié : la cohérence transactionnelle. Si vous archivez en masse (10k SKUs), évitez 10k updates unitaires côté PHP. Faites du batch (requêtes INSERT ... ON DUPLICATE KEY UPDATE) et encapsulez côté SQL. Ça réduit le temps de lock et le risque de timeouts.
Exemple de requête d’UPSERT (batch) typique, efficace et facile à rejouer :
INSERT INTO ps_product_archive
(id_product, id_shop, archived, archived_at, archived_by, reason, replacement_id_product)
VALUES
(123, 1, 1, NOW(), 42, 'Fin de série', 456),
(124, 1, 1, NOW(), 42, 'Saisonnier', NULL)
ON DUPLICATE KEY UPDATE
archived = VALUES(archived),
archived_at = VALUES(archived_at),
archived_by = VALUES(archived_by),
reason = VALUES(reason),
replacement_id_product = VALUES(replacement_id_product);
Enfin, si vous journalisez archived_by (employé) et éventuellement une IP en log, pensez “minimisation” : limitez la durée de conservation des logs, et documentez-la (utile en audit interne et conforme à une logique RGPD). Un module d’archivage catalogue n’a pas vocation à conserver indéfiniment des données de sécurité détaillées si elles ne sont pas nécessaires.
Implémentation Back-Office PrestaShop 8.1 / 9.1 : grilles, formulaires, actions de masse
Pour la grille produits, PrestaShop fournit des hooks Symfony pour modifier définition, requêtes et données. Le hook le plus utile pour enrichir l’affichage et injecter un champ calculé est actionProductGridDataModifier (voir article interne sur actionProductGridDataModifier). En 8/9, une implémentation propre suit généralement ce schéma :
actionProductGridQueryBuilderModifier: ajout duLEFT JOINsurps_product_archiveet des conditions selon filtre ;actionProductGridDefinitionModifier: ajout de la colonne “Archivé” + filtre booléen ;actionProductGridDataModifier: formatage de la valeur (badge, date, raison).
Exemple (simplifié) d’ajout de contrainte d’exclusion par défaut côté query builder :
public function hookActionProductGridQueryBuilderModifier(array $params)
{
$qb = $params['search_query_builder'];
$searchCriteria = $params['search_criteria'];
$filters = $searchCriteria->getFilters();
$idShop = (int) $this->context->shop->id;
$qb->leftJoin('p', _DB_PREFIX_.'product_archive', 'pa',
'pa.id_product = p.id_product AND pa.id_shop = '.(int)$idShop
);
// Par défaut: on n'affiche pas les archivés
if (!isset($filters['archived'])) {
$qb->andWhere('(pa.archived IS NULL OR pa.archived = 0)');
return;
}
if ($filters['archived'] === '1') {
$qb->andWhere('pa.archived = 1');
} elseif ($filters['archived'] === '0') {
$qb->andWhere('(pa.archived IS NULL OR pa.archived = 0)');
}
}
Le point critique ici n’est pas le code, c’est le contrat de données : la grille produit est déjà lourde (joins catégorie par défaut, calculs de stock, etc.). Chaque join supplémentaire se paye. Mesurez (profiling + EXPLAIN), et indexez.
Pour le formulaire produit, vous avez deux options :
- approche minimaliste : un écran de module “Archiver” accessible depuis la grille (action de ligne), sans toucher au formulaire produit natif ;
- approche intégrée : ajouter un switch “Archivé” directement dans le formulaire produit via
actionProductFormBuilderModifier, puis persister via les hooks de handler (actionAfterUpdateProductFormHandler,actionAfterCreateProductFormHandler).
L’intégration est plus ergonomique, mais plus exposée aux changements de structure du formulaire entre 8.x et 9.x. Sur des projets “upgrade-friendly”, l’approche minimaliste (action dédiée) est souvent plus stable… tout en restant rapide à utiliser si vous ajoutez une action de masse.
Sur les actions de masse, ne sous-estimez pas les permissions. Un archivage est une action à impact business (SEO, ventes). Créez une permission dédiée dans votre module et refusez l’action si l’employé n’a pas ce droit. Pour les shops à exigences de traçabilité, loggez aussi dans une table ps_product_archive_log (qui a archivé quoi, quand, depuis quelle IP), ou exportez vers votre SIEM (voir article sécurité et journalisation).
Astuce UX BO qui évite des erreurs coûteuses : ajoutez dans la grille un affichage compact du “remplaçant” (référence + lien BO), mais seulement quand archived=1. Ça évite de recharger la grille en permanence, et ça rend l’archivage exploitable par des équipes non techniques (catalog managers).
Front-Office : exclure du catalogue sans casser l’historique, les caches et la recherche
Si vous choisissez l’archive « SEO » (produit consultable, non listé), vous devez contrôler plusieurs surfaces :
- catégories, recherche, nouveautés, best-sellers, cross-sell, “produits similaires” ;
- modules de navigation à facettes ;
- feeds (Google Merchant, marketplaces), APIs, exports.
Le cœur n’offre pas un switch global “exclude archived”. Vous devrez donc appliquer la règle soit en amont (requêtes SQL / repositories), soit via hooks des modules (ex. facettes), soit via un index de recherche externe.
Pour la recherche, l’écart entre “ça marche sur mon shop” et “ça tient à 500k produits” est violent. Si vous êtes sur recherche native, l’indexation peut réintroduire des produits si vous les laissez active=1. Si vous êtes sur un moteur externe, vous devez implémenter une règle d’exclusion au niveau index (champ archived filtrable). L’article interne sur ElasticSearch en gros catalogue donne un bon cadre.
Sur la gestion SEO, arrêtez de traiter “archivé” comme une simple désactivation. Vous avez trois patterns réalistes :
- 301 vers produit remplaçant si l’intention utilisateur est identique (nouveau modèle).
- 200 + noindex si la page reste utile (doc, compatibilités, pièces).
- 410 si le contenu n’a plus de valeur et ne doit pas revenir.
Une grille de décision très opérationnelle (à valider avec marketing/SEO) :
| Situation | Recommandation | Ce qu’il faut absolument faire |
|---|---|---|
| Nouveau modèle remplaçant clairement l’ancien | 301 vers remplaçant | éviter chaînes de redirections, afficher le remplaçant aussi pour les liens internes |
| Produit non vendu mais fiche utile (SAV, compatibilités) | 200 + noindex |
retirer des listings/recherche interne, message FO explicite, cohérence sitemap |
| Produit retiré définitivement (ou contenu sans valeur) | 410 | retirer du sitemap, vérifier qu’aucun lien interne ne pointe encore dessus |
Le 301 demande de maîtriser vos redirections (au niveau PrestaShop ou proxy), et d’éviter les chaînes. Le noindex nécessite un contrôle de la balise robots (thème / hook header), et la cohérence avec sitemap.
Côté UX (notamment en contexte FR/UE), le message compte : si l’utilisateur arrive via un lien externe, vous devez éviter la “page morte” qui dégrade la confiance. Un bandeau simple fonctionne bien : « Ce produit n’est plus commercialisé. Voici le modèle le plus proche / la gamme actuelle. » + lien direct. Ce n’est pas du SEO “magique”, c’est du taux de conversion et du support en moins.
Si votre shop est caché agressivement (Varnish, reverse proxy), l’archivage doit invalider les pages concernées (fiche produit, catégories impactées, recherche). Sinon vous “archivez” en base mais le front continue de servir l’ancien état. Lisez au minimum un guide d’ESI/invalidation — et couplez ça avec une discipline de mesure côté perf (TTFB) : TTFB PrestaShop
Enfin, si vous exposez des données structurées (schema.org/Product), attention à la cohérence : une fiche “archivée SEO” ne devrait pas annoncer une disponibilité incohérente (ex. InStock). À défaut de supprimer totalement le balisage, assurez-vous que le statut (disponibilité / offre) reflète l’archivage et n’envoie pas de signaux contradictoires.
Performance, volumétrie et maintenance : archiver n’est pas “gratuit”
Archiver sans supprimer maintient la volumétrie : images, déclinaisons, index, tables de liaison. Ça ne pose pas de problème tant que vos requêtes ignorent correctement l’archive, et que vos index sont alignés. Ce que vous cherchez, c’est réduire la surface “active” (affichée, indexée, vendable) sans rendre le schéma ingérable.
Le point le plus rentable en perf est souvent la grille produits du BO : sur de gros catalogues, elle devient une source de lenteur (joins + tri + filtres). Ajoutez votre join product_archive, mais faites-le avec des index et des conditions sargables. Dans MySQL, la règle reste simple : si vous filtrez par (id_shop, archived), indexez ces colonnes dans cet ordre.
Côté maintenance, prévoyez une routine :
- job CRON pour archiver automatiquement selon règles (stock à 0 depuis X jours, date de fin, flag ERP) ;
- job CRON pour désindexer (search interne / Elastic) ;
- job CRON pour nettoyer les caches applicatifs.
Sur des catalogues volumineux, un détail pratique : faites des lots (par exemple 500 à 2000 produits par batch selon votre infra) et mesurez la durée. L’objectif n’est pas “le batch le plus gros”, c’est un batch qui :
- reste sous vos timeouts PHP/nginx,
- minimise les locks,
- se rejoue facilement (idempotent) en cas d’échec.
Pour cadrer la partie “hygiène base”, vous pouvez vous appuyer sur la routine de maintenance et nettoyage automatisé.
Si vous synchronisez votre catalogue avec un ERP (Odoo, PIM), l’archivage doit être un état synchronisable, pas une action locale. Sinon l’ERP va republier. Sur des intégrations bi-directionnelles (stock, référentiel produit), l’état “archived” devrait se propager comme un attribut, et non comme un effet secondaire (ex. active=0). Pour les architectures de sync, voir : intégration bi-directionnelle
Un bénéfice souvent sous-estimé (notamment en Europe) : conserver l’historique produit aide aussi à conserver l’historique des prix et de contenu associé. Sans entrer dans du juridique, c’est typiquement le genre de données qui devient utile quand on doit justifier des évolutions tarifaires, gérer des litiges, ou simplement analyser les performances par cycles de vie.
Déploiement, sécurité et qualité : éviter le module “qui marche jusqu’au prochain upgrade”
En 2026, un module qui touche au catalogue doit être pensé “upgrade-safe”. Évitez les overrides sur les classes cœur (Product, Search, controllers) : c’est le chemin le plus court vers des conflits à chaque mise à jour PrestaShop ou module tiers. Préférez les hooks, les services Symfony, et une table dédiée. Si vous devez absolument modifier un comportement central (ex. indexation), encapsulez-le derrière une feature flag et documentez la compatibilité par versions.
Au déploiement, prenez aussi position sur un point qui revient souvent : désinstallation. Un module d’archivage peut raisonnablement proposer :
- soit de conserver la table d’archive (pour ne pas “perdre” un état métier),
- soit de fournir une commande/outil explicite d’effacement (et seulement là, de supprimer les tables). Ce choix doit être documenté, sinon vous aurez des surprises en maintenance (en particulier lors de migrations de shop ou de changements d’intégrateur).
Sur l’aspect sécurité : l’archivage est une surface d’abus interne (masquer des produits, casser des ventes) et externe (endpoints non authentifiés si vous exposez une API). Appliquez des contrôles d’accès systématiques, validez les entrées (reason, replacement), et journalisez. Gardez aussi un œil sur la supply chain : un module mal maintenu peut devenir un point d’entrée. La posture globale est bien résumée dans les articles sécurité 2026 et, sur l’exemple concret d’une CVE module, exemple CVE module.
Enfin, traitez ça comme du code produit : tests et CI. À minima : tests fonctionnels sur (a) ajout au panier, (b) accès fiche produit, (c) exclusion catégories/recherche, (d) export feed. Ajoutez un test “anti-régression” typique : un produit archivé ne doit jamais remonter dans la recherche, même après réindexation. Automatisez la qualité statique (PHPStan) et les refactors sûrs (Rector) pour rester compatible PHP/PrestaShop : PHPStan et Rector. Et n’oubliez pas de documenter explicitement les versions supportées (par exemple PrestaShop 8.1.x et 9.1.x, PHP 8.1–8.3/8.4 selon votre stack) en cohérence avec les contraintes runtime : compatibilité PrestaShop 9.1 et PHP.
Si vous cherchez un critère simple pour valider votre module PrestaShop Archive Products : il doit permettre d’archiver sans suppression, sans overrides, avec un état multi-shop, une UX BO exploitable (filtre + bulk), et une stratégie FO/SEO explicite (noindex/301/410). Tout le reste (automatisation, logs, API) est ce qui différencie un module “qui cache” d’un module qui résout réellement la dette catalogue.




