Sin impuesto Con impuesto

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.

Jesús Meilán
Updated: 26/09/2026 98
Structure of a modern module for PrestaShop 8 and 9
Share:

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.

Watch out for shared libraries: if two modules each include their own copy of an SDK (Stripe’s is the classic case) and both are loaded in the same request, PHP fails with “Cannot declare class”. Load the dependencies only if the class does not already exist, or isolate it with a namespace prefix.

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.php in 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.

Loading...
Cookie consent
Back to top