Ajouter un champ image personnalisé aux catégories PrestaShop
Guide complet pour ajouter un second champ image aux catégories PrestaShop 1.7 et 8.x : override de classe, migration SQL, back-office et affichage front.
En bref : Ajoutez un second champ image aux catégories PrestaShop via un override de la classe Category (PS 1.6/1.7) ou un module Symfony (PS 8.x), en modifiant la base de données, le back-office et les templates front-end.
Pourquoi ajouter une seconde image aux catégories ?
Le système natif de PrestaShop ne propose qu'une seule image par catégorie, accompagnée éventuellement d'une vignette (thumbnail). C'est souvent insuffisant pour les besoins de webdesign modernes : image de fond pleine largeur dans le header, visuel d'ambiance distinct de la vignette de navigation, bannière promotionnelle saisonnière…
L'ajout d'un champ id_image2 aux catégories permet de gérer deux visuels indépendants directement depuis le back-office, sans recourir à un module tiers. Ce tutoriel détaille chaque étape, du schéma SQL jusqu'à l'affichage front-end.
Prérequis et préparation
Avant toute modification structurelle :
- **Activez le mode debug** pour ne jamais rester bloqué sur une page blanche sans message d'erreur
- Sauvegardez votre base de données
- Travaillez sur un environnement de développement ou de préprod
Activer le mode développeur
PrestaShop 1.6 :
// config/defines.inc.php
if (!defined('_PS_MODE_DEV_'))
define('_PS_MODE_DEV_', true);
PrestaShop 1.7 / 8.x :
// .env ou app/config/parameters.php
// Passez PS_DEV_MODE à true
define('_PS_MODE_DEV_', true);
Sur PrestaShop 8.x, vous pouvez également utiliser le fichier .env à la racine :
APP_DEBUG=1
APP_ENV=dev
Cette activation est indispensable : sans elle, une erreur PHP produit une page blanche inexploitable.
Étape 1 : Modification de la base de données
Ajoutez la colonne id_image2 à la table des catégories :
ALTER TABLE `ps_category`
ADD `id_image2` INT(10) UNSIGNED DEFAULT NULL AFTER `id_parent`;
Cette colonne stockera l'identifiant de la seconde image. Le préfixe ps_ correspond au préfixe par défaut de vos tables — adaptez-le si nécessaire.
Étape 2 : Override de la classe Category
Créez le fichier d'override pour étendre le modèle Category avec la gestion de la nouvelle image.
Chemin : override/classes/Category.php
<?php
/**
* Override Category - Ajout d'un second champ image
*/
class Category extends CategoryCore
{
/** @var int|null Identifiant de la seconde image */
public $id_image2;
/**
* Définition enrichie avec le nouveau champ
*/
public function __construct($id_category = null, $id_lang = null, $id_shop = null)
{
// Déclaration du champ avant l'appel parent
self::$definition['fields']['id_image2'] = [
'type' => self::TYPE_INT,
'validate' => 'isUnsignedId',
];
parent::__construct($id_category, $id_lang, $id_shop);
}
/**
* Ajoute (upload) la seconde image
*
* @return bool
*/
public function addImage2()
{
if (!isset($_FILES['image2']) || $_FILES['image2']['error'] !== UPLOAD_ERR_OK) {
return false;
}
$tmp_name = $_FILES['image2']['tmp_name'];
$target_dir = _PS_CAT_IMG_DIR_;
$image_name = (int)$this->id . '-2';
// Génération des différents formats d'image
$image_types = ImageType::getImagesTypes('categories');
if (!ImageManager::resize($tmp_name, $target_dir . $image_name . '.jpg')) {
return false;
}
foreach ($image_types as $image_type) {
ImageManager::resize(
$tmp_name,
$target_dir . $image_name . '-' . stripslashes($image_type['name']) . '.jpg',
(int)$image_type['width'],
(int)$image_type['height']
);
}
// Mise à jour de l'identifiant en base
$this->id_image2 = 1;
return $this->update();
}
/**
* Supprime la seconde image et toutes ses déclinaisons
*
* @return bool
*/
public function deleteImage2()
{
$target_dir = _PS_CAT_IMG_DIR_;
$image_name = (int)$this->id . '-2';
// Suppression de l'image originale
$path = $target_dir . $image_name . '.jpg';
if (file_exists($path)) {
@unlink($path);
}
// Suppression des déclinaisons
$image_types = ImageType::getImagesTypes('categories');
foreach ($image_types as $image_type) {
$path = $target_dir . $image_name . '-' . stripslashes($image_type['name']) . '.jpg';
if (file_exists($path)) {
@unlink($path);
}
}
$this->id_image2 = null;
return $this->update();
}
/**
* Retourne le chemin de la seconde image
*
* @param string $type Format d'image (ex: 'medium_default')
* @return string|false
*/
public function getImage2Url($type = 'category_default')
{
$path = _PS_CAT_IMG_DIR_ . (int)$this->id . '-2-' . $type . '.jpg';
if (file_exists($path)) {
return _THEME_CAT_DIR_ . (int)$this->id . '-2-' . $type . '.jpg';
}
return false;
}
}
Point critique : le cache des classes
Après avoir créé ou modifié un override, vous devez supprimer le fichier d'index des classes pour que PrestaShop prenne en compte vos modifications :
# PrestaShop 1.6
rm var/cache/*/class_index.php
# PrestaShop 1.7 / 8.x
rm var/cache/dev/class_index.php
rm var/cache/prod/class_index.php
Sans cette suppression, vous obtiendrez l'erreur :
Call to undefined method Category::deleteImage2
Cette erreur signifie que PHP utilise encore la classe CategoryCore en cache au lieu de votre override. C'est le piège le plus fréquent de cette manipulation.
Étape 3 : Modification du back-office
Pour que l'administrateur puisse uploader la seconde image, il faut ajouter le champ dans le formulaire de gestion des catégories.
PrestaShop 1.6 (AdminCategoriesController)
Créez l'override du contrôleur :
Chemin : override/controllers/admin/AdminCategoriesController.php
<?php
class AdminCategoriesController extends AdminCategoriesControllerCore
{
public function renderForm()
{
// Appel du formulaire parent
$form = parent::renderForm();
// Injection du champ image2 dans le HTML
// (méthode simplifiée — en production, utilisez le système de hooks)
$image2_field = '<div class="form-group">';
$image2_field .= '<label class="control-label col-lg-3">Image secondaire</label>';
$image2_field .= '<div class="col-lg-9">';
$image2_field .= '<input type="file" name="image2" accept="image/*" />';
if ($this->object && $this->object->id_image2) {
$image2_field .= '<p class="help-block"><img src="'
. _THEME_CAT_DIR_ . (int)$this->object->id . '-2-medium_default.jpg'
. '" style="max-width:200px" /></p>';
}
$image2_field .= '</div></div>';
// Insertion avant le bouton de soumission
$form = str_replace('id="category_form_submit_btn"', $image2_field . 'id="category_form_submit_btn"', $form);
return $form;
}
public function postProcess()
{
parent::postProcess();
if (Tools::isSubmit('submitAddcategory') || Tools::isSubmit('submitAddcategoryAndStay')) {
$category = new Category((int)Tools::getValue('id_category'));
if (Validate::isLoadedObject($category)) {
$category->addImage2();
}
}
}
}
PrestaShop 8.x (Symfony)
Sur PrestaShop 8.x, le back-office utilise Symfony. L'approche recommandée est de créer un module qui utilise les hooks de formulaire :
<?php
// modules/mymodule/mymodule.php (extrait)
public function hookActionCategoryFormBuilderModifier(array $params)
{
/** @var FormBuilderInterface $formBuilder */
$formBuilder = $params['form_builder'];
$formBuilder->add('image2', FileType::class, [
'label' => $this->trans('Image secondaire', [], 'Modules.Mymodule.Admin'),
'required' => false,
'attr' => ['accept' => 'image/*'],
]);
// Pré-remplissage si l'image existe
$categoryId = $params['id'];
$params['data']['image2'] = null;
$formBuilder->setData($params['data']);
}
public function hookActionAfterUpdateCategoryFormHandler(array $params)
{
$this->handleImage2Upload($params);
}
public function hookActionAfterCreateCategoryFormHandler(array $params)
{
$this->handleImage2Upload($params);
}
private function handleImage2Upload(array $params)
{
$categoryId = $params['id'];
$category = new Category((int)$categoryId);
if (Validate::isLoadedObject($category)) {
$category->addImage2();
}
}
Cette approche par module est nettement plus propre et survivra aux mises à jour de PrestaShop.
Étape 4 : Affichage front-end
Affichage dans le template de catégorie
PrestaShop 1.6 (Smarty) :
{* Image secondaire de la catégorie courante *}
{if $category->id_image2}
<img
class="category-header-bg"
src="{$link->getCatImageLink($category->link_rewrite, $category->id_image2, 'category_default')|escape:'html':'UTF-8'}"
alt="{$category->name|escape:'html':'UTF-8'}"
loading="lazy"
/>
{/if}
Attention à la variable utilisée : $category pour la catégorie en cours, $subcategory dans une boucle sur les sous-catégories. Confondre les deux est une source d'erreur classique :
{* CORRECT — dans une boucle de sous-catégories *}
{foreach from=$subcategories item=subcategory}
<div class="subcategory-card">
{* Image principale *}
<img src="{$link->getCatImageLink($subcategory.link_rewrite, $subcategory.id_image, 'medium_default')|escape:'html':'UTF-8'}" alt="{$subcategory.name|escape:'html':'UTF-8'}" />
{* Image secondaire *}
{if isset($subcategory.id_image2) && $subcategory.id_image2}
<img src="{$link->getCatImageLink($subcategory.link_rewrite, $subcategory.id_image2, 'medium_default')|escape:'html':'UTF-8'}" alt="" />
{/if}
</div>
{/foreach}
PrestaShop 8.x (Twig) :
Sur les versions modernes, l'image peut être exposée via un module qui enrichit les variables de template :
{# templates/catalog/listing/category.html.twig #}
{% if category.image2_url is defined and category.image2_url %}
<div class="category-banner">
<img
src="{{ category.image2_url }}"
alt="{{ category.name }}"
loading="lazy"
class="img-fluid"
/>
</div>
{% endif %}
Étape 5 : Convention de nommage des fichiers image
Les images sont stockées dans /img/c/ avec la convention suivante :
Par exemple, pour la catégorie 5 : 5-2.jpg, 5-2-category_default.jpg, etc.
Bonnes pratiques et pièges à éviter
1. Toujours vider le cache des classes
L'erreur Call to undefined method après un override est quasi systématique lors du premier essai. Supprimez class_index.php dans tous les dossiers de cache.
2. Gestion robuste des uploads
Vérifiez toujours la validité du fichier uploadé (type MIME, taille, dimensions) avant traitement. Utilisez ImageManager::validateUpload() fourni par PrestaShop.
3. Privilégiez un module sur PrestaShop 8.x
Les overrides fonctionnent encore sur PS 8.x, mais la tendance est à leur suppression progressive. Pour un projet pérenne, encapsulez cette fonctionnalité dans un module dédié utilisant les hooks Symfony.
4. Ne confondez pas $category et $subcategory
Dans les templates Smarty, $category désigne la catégorie courante (la page consultée), tandis que $subcategory est l'itérateur dans un foreach sur les sous-catégories. Utiliser l'un à la place de l'autre affichera la mauvaise image — ou rien du tout.
5. Régénération des miniatures
Après avoir ajouté les images, pensez à régénérer les miniatures depuis Préférences > Images dans le back-office, ou via la ligne de commande :
# PrestaShop 8.x
php bin/console prestashop:image:regenerate --type=categories
Aller plus loin : image WebP et lazy loading
Sur PrestaShop 8.x, le support natif du format WebP est activable. Pour vos images secondaires, appliquez la même logique :
// Génération WebP en plus du JPEG
if (function_exists('imagewebp')) {
ImageManager::resize(
$tmp_name,
$target_dir . $image_name . '-' . $image_type['name'] . '.webp',
(int)$image_type['width'],
(int)$image_type['height'],
'webp'
);
}
Combinez avec l'élément en front pour servir le format optimal :
<picture>
<source srcset="/img/c/5-2-category_default.webp" type="image/webp">
<img src="/img/c/5-2-category_default.jpg" alt="" loading="lazy">
</picture>
Questions fréquentes
Tout ce que vous devez savoir sur ce sujet.
Un projet PrestaShop ?
Discutons-en directement.
193 projets livrés
Lire sur le blog

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.