Structure of a modern module for PrestaShop 8 and 9
A 2026 module is not one giant PHP file. We show you the structure we use so it is maintainable, passes the Addons validator and survives updates.
Many modules are still written as in PrestaShop 1.6: a main file thousands of lines long, hand-written SQL and HelperForm forms. They work, but they are expensive to maintain. This is the structure we follow in our modules for PrestaShop 8 and 9.
The folder tree
mimodulo/
├── mimodulo.php # main class: installation, hooks, little else
├── composer.json # PSR-4 autoload of the namespace
├── config/
│ ├── services.yml # Symfony services
│ └── routes.yml # back-office routes
├── src/
│ ├── Controller/Admin/ # Symfony controllers
│ ├── Entity/ or Repository/
│ └── Service/ # business logic, testable
├── views/
│ ├── templates/hook/ # front-office Smarty templates
│ ├── templates/admin/ # back-office Twig templates
│ ├── css/ js/ img/
├── upgrade/ # upgrade-1.1.0.php, upgrade-1.2.0.php…
├── translations/
└── logo.png
A thin main class
The mimodulo.php file should do no more than declare the module, install it and delegate. The real logic goes into classes in src/ that can be tested separately:
public function hookActionValidateOrder(array $params)
{
$this->get('mimodulo.order_notifier')->notify($params['order']);
}
Composer and namespace
{
"name": "rekire/mimodulo",
"autoload": { "psr-4": { "Rekire\\MiModulo\\": "src/" } },
"config": { "prepend-autoloader": false },
"type": "prestashop-module"
}
The prepend-autoloader: false option stops your autoloader from jumping ahead of PrestaShop’s, which can cause version conflicts with libraries the core already ships.
Back office with Symfony
In PrestaShop 8 and 9, configuration screens can be Symfony controllers with Symfony forms and Twig templates, declared in config/routes.yml. That way they integrate with the back office menu, permissions and CSRF tokens without reinventing them.
Upgrades from day one
Every version that changes the database, registers new hooks or creates tabs comes with its upgrade/upgrade-X.Y.Z.php. And that script must be idempotent: if it runs twice, it duplicates nothing. That is what prevents duplicate tabs in the menu or columns that fail to be created.
Validation before publishing
- Licence header in every file.
- Escaping of variables in Smarty (
|escape:'html':'UTF-8'). - No functions banned by the Addons validator.
index.phpin every folder.- Tests on at least two PrestaShop versions and with debug mode on: warnings you do not see in production do count in the compatibility report.
With this structure, a module can grow for years without turning into a file nobody dares to touch.