---
title: The Main Module PHP File
section: modules
slug: main-file
description: "Complete reference for the main PHP file of a PrestaShop module: class properties, constructor, install/uninstall methods, hooks, configuration pages and SQL setup."
keywords: prestashop modulo php clase principal install uninstall hook constructor version
last_updated: 2025-01-01
source_url: "https://ayudaprestashop.es/modules/main-file"
---

# The Main Module PHP File

> Complete reference for the main PHP file of a PrestaShop module: class properties, constructor, install/uninstall methods, hooks, configuration pages and SQL setup.

Every PrestaShop module is built around a single main PHP file whose name matches the module folder. If your module folder is `mymodule/`, the file must be `mymodule/mymodule.php`. This file contains a class that extends `Module` (or a specialisation such as `PaymentModule`) and acts as the entry point for installation, uninstallation, hook dispatch and configuration.

> **[!] Security header required**
>
> Always add `if (!defined('_PS_VERSION_')) { exit; }` immediately after the opening `<?php` tag. Without it anyone can execute the file directly via the browser.

## Module Class Structure

A minimal module class must extend `Module`, declare a set of public properties that PrestaShop reads at install-time, and implement a constructor that populates those properties.

## Required Class Properties

| Property | Type | Description |
| --- | --- | --- |
| $name | string | Unique technical name — lowercase, no spaces, matches folder/file name |
| $tab | string | Back-office menu tab (e.g. `administration`, `front_office_features`) |
| $version | string | Module version string, e.g. `'1.0.0'` |
| $author | string | Author name shown in the module manager |
| $need_instance | int | 0 or 1 — whether to instantiate on every page load |
| $ps_versions_compliancy | array | Keys `min` and `max` for PrestaShop version range |
| $bootstrap | bool | true if the module configuration page uses Bootstrap 3/4 layout |

## The Constructor

The constructor assigns the properties above and then calls `parent::__construct()`. After the parent call you have access to `$this->l()` for translations, so the `displayName`, `description` and `confirmUninstall` strings should be set **after** the parent call.

*mymodule.php*

```php
<?php
if (!defined('_PS_VERSION_')) {
    exit;
}

class MyModule extends Module
{
    public function __construct()
    {
        $this->name          = 'mymodule';
        $this->tab           = 'front_office_features';
        $this->version       = '1.0.0';
        $this->author        = 'My Company';
        $this->need_instance = 0;
        $this->ps_versions_compliancy = [
            'min' => '1.7.0.0',
            'max' => _PS_VERSION_,
        ];
        $this->bootstrap = true;

        parent::__construct();

        $this->displayName     = $this->l('My Module');
        $this->description     = $this->l('A short description of what this module does.');
        $this->confirmUninstall = $this->l('Are you sure you want to uninstall?');
    }
}
```

## install() and uninstall()

Both methods must return a boolean. Call `parent::install()` / `parent::uninstall()` first and chain your own operations. If any step fails, return `false` so PrestaShop rolls back and shows an error.

*mymodule.php*

```php
<?php
public function install()
{
    return parent::install()
        && $this->registerHook('displayHeader')
        && $this->registerHook('displayFooter')
        && $this->createTable()
        && Configuration::updateValue('MYMODULE_ACTIVE', 1);
}

public function uninstall()
{
    return parent::uninstall()
        && $this->dropTable()
        && Configuration::deleteByName('MYMODULE_ACTIVE');
}
```

## Registering Hooks

You can register hooks at install time (as above) or lazily at runtime by calling `$this->registerHook()` from `getContent()`. The hook handler method must be named `hook` + the hook name in CamelCase.

*mymodule.php*

```php
<?php
// Hook handler — called by PrestaShop when displayHeader fires
public function hookDisplayHeader($params)
{
    // $params contains context-specific data depending on the hook
    $this->context->controller->addCSS($this->_path . 'views/css/mymodule.css');
    $this->context->controller->addJS($this->_path . 'views/js/mymodule.js');
}

public function hookDisplayFooter($params)
{
    $this->smarty->assign([
        'mymodule_active' => (bool) Configuration::get('MYMODULE_ACTIVE'),
        'shop_name'       => Configuration::get('PS_SHOP_NAME'),
    ]);

    return $this->display(__FILE__, 'views/templates/hook/footer.tpl');
}
```

## Creating Database Tables

Use `Db::getInstance()->execute()` inside `install()` to create your custom tables. Always prefix the table name with `_DB_PREFIX_` and use `IF NOT EXISTS` to avoid errors on re-install. Drop the table on uninstall.

*mymodule.php*

```php
<?php
private function createTable()
{
    $sql = '
        CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'mymodule_entry` (
            `id_entry`   INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
            `id_shop`    INT(11) UNSIGNED NOT NULL DEFAULT 1,
            `label`      VARCHAR(255) NOT NULL,
            `content`    TEXT,
            `active`     TINYINT(1) NOT NULL DEFAULT 1,
            `date_add`   DATETIME NOT NULL,
            `date_upd`   DATETIME NOT NULL,
            PRIMARY KEY (`id_entry`),
            KEY `id_shop` (`id_shop`)
        ) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=utf8mb4;
    ';

    return Db::getInstance()->execute($sql);
}

private function dropTable()
{
    return Db::getInstance()->execute(
        'DROP TABLE IF EXISTS `' . _DB_PREFIX_ . 'mymodule_entry`'
    );
}
```

## getContent() – Configuration Page

Defining `getContent()` makes the **Configure** button visible in the module list. The method should handle form submission (POST) and then render the form. Always redirect after a successful POST (`Tools::redirectAdmin`) to avoid double-submission.

*mymodule.php*

```php
<?php
public function getContent()
{
    $output = '';

    if (Tools::isSubmit('submit_mymodule')) {
        $active = (int) Tools::getValue('MYMODULE_ACTIVE');
        Configuration::updateValue('MYMODULE_ACTIVE', $active);
        $output .= $this->displayConfirmation($this->l('Settings saved.'));
    }

    return $output . $this->renderForm();
}

private function renderForm()
{
    $helper = new HelperForm();
    $helper->module            = $this;
    $helper->name_controller   = $this->name;
    $helper->identifier        = $this->identifier;
    $helper->token             = Tools::getAdminTokenLite('AdminModules');
    $helper->currentIndex      = AdminController::$currentIndex . '&configure=' . $this->name;
    $helper->default_form_language    = (int) Configuration::get('PS_LANG_DEFAULT');
    $helper->allow_employee_form_lang = Configuration::get('PS_BO_ALLOW_EMPLOYEE_FORM_LANG');
    $helper->title    = $this->displayName;
    $helper->submit_action = 'submit_mymodule';

    $helper->fields_value['MYMODULE_ACTIVE'] = Configuration::get('MYMODULE_ACTIVE');

    $form = [[
        'form' => [
            'legend' => ['title' => $this->l('Settings'), 'icon' => 'icon-cogs'],
            'input'  => [
                [
                    'type'    => 'switch',
                    'label'   => $this->l('Enable module'),
                    'name'    => 'MYMODULE_ACTIVE',
                    'values'  => [
                        ['id' => 'active_on',  'value' => 1, 'label' => $this->l('Yes')],
                        ['id' => 'active_off', 'value' => 0, 'label' => $this->l('No')],
                    ],
                ],
            ],
            'submit' => ['title' => $this->l('Save'), 'class' => 'btn btn-default pull-right'],
        ],
    ]];

    return $helper->generateForm($form);
}
```

## Tabs (Back-Office Menu Items)

Declare the `$tabs` property to automatically register back-office menu entries when the module is installed. Each entry maps a controller class name to a label and parent tab.

*mymodule.php*

```php
<?php
// Inside the class, at the property level:
public $tabs = [
    [
        'name'       => ['en' => 'My Module', 'es' => 'Mi Módulo'],
        'class_name' => 'AdminMyModule',
        'parent_class_name' => 'AdminCatalog',
        'visible'    => true,
        'icon'       => 'settings_applications',
    ],
];

// PrestaShop 1.7.7+ also supports route-based tabs:
// 'route_name' => 'admin_mymodule_index',
```

## Complete Working Example

*mymodule/mymodule.php*

```php
<?php
if (!defined('_PS_VERSION_')) {
    exit;
}

class MyModule extends Module
{
    public $tabs = [
        [
            'name'              => ['en' => 'My Module', 'es' => 'Mi Módulo'],
            'class_name'        => 'AdminMyModule',
            'parent_class_name' => 'AdminCatalog',
            'visible'           => true,
        ],
    ];

    public function __construct()
    {
        $this->name          = 'mymodule';
        $this->tab           = 'front_office_features';
        $this->version       = '1.0.0';
        $this->author        = 'My Company';
        $this->need_instance = 0;
        $this->ps_versions_compliancy = ['min' => '1.7.6.0', 'max' => _PS_VERSION_];
        $this->bootstrap     = true;

        parent::__construct();

        $this->displayName      = $this->l('My Module');
        $this->description      = $this->l('Demo module for PrestaShop developers.');
        $this->confirmUninstall = $this->l('Delete all module data?');
    }

    public function install()
    {
        return parent::install()
            && $this->registerHook('displayHeader')
            && $this->registerHook('displayFooter')
            && $this->createTable()
            && Configuration::updateValue('MYMODULE_ACTIVE', 1)
            && Configuration::updateValue('MYMODULE_TEXT', '');
    }

    public function uninstall()
    {
        return parent::uninstall()
            && $this->dropTable()
            && Configuration::deleteByName('MYMODULE_ACTIVE')
            && Configuration::deleteByName('MYMODULE_TEXT');
    }

    private function createTable()
    {
        return Db::getInstance()->execute('
            CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'mymodule_entry` (
                `id_entry` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
                `id_shop`  INT(11) UNSIGNED NOT NULL DEFAULT 1,
                `label`    VARCHAR(255) NOT NULL,
                `active`   TINYINT(1) NOT NULL DEFAULT 1,
                `date_add` DATETIME NOT NULL,
                `date_upd` DATETIME NOT NULL,
                PRIMARY KEY (`id_entry`)
            ) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=utf8mb4
        ');
    }

    private function dropTable()
    {
        return Db::getInstance()->execute(
            'DROP TABLE IF EXISTS `' . _DB_PREFIX_ . 'mymodule_entry`'
        );
    }

    public function getContent()
    {
        $output = '';
        if (Tools::isSubmit('submit_mymodule')) {
            Configuration::updateValue('MYMODULE_ACTIVE', (int) Tools::getValue('MYMODULE_ACTIVE'));
            Configuration::updateValue('MYMODULE_TEXT', pSQL(Tools::getValue('MYMODULE_TEXT')));
            $output .= $this->displayConfirmation($this->l('Settings saved.'));
        }
        return $output . $this->renderForm();
    }

    private function renderForm()
    {
        $helper = new HelperForm();
        $helper->module          = $this;
        $helper->token           = Tools::getAdminTokenLite('AdminModules');
        $helper->currentIndex    = AdminController::$currentIndex . '&configure=' . $this->name;
        $helper->submit_action   = 'submit_mymodule';
        $helper->fields_value    = [
            'MYMODULE_ACTIVE' => Configuration::get('MYMODULE_ACTIVE'),
            'MYMODULE_TEXT'   => Configuration::get('MYMODULE_TEXT'),
        ];

        return $helper->generateForm([[
            'form' => [
                'legend' => ['title' => $this->l('Settings')],
                'input'  => [
                    ['type' => 'switch', 'label' => $this->l('Active'), 'name' => 'MYMODULE_ACTIVE',
                     'values' => [['id'=>'on','value'=>1,'label'=>$this->l('Yes')],['id'=>'off','value'=>0,'label'=>$this->l('No')]]],
                    ['type' => 'text', 'label' => $this->l('Custom text'), 'name' => 'MYMODULE_TEXT'],
                ],
                'submit' => ['title' => $this->l('Save')],
            ],
        ]]);
    }

    public function hookDisplayHeader($params)
    {
        if (!Configuration::get('MYMODULE_ACTIVE')) {
            return;
        }
        $this->context->controller->addCSS($this->_path . 'views/css/mymodule.css');
    }

    public function hookDisplayFooter($params)
    {
        if (!Configuration::get('MYMODULE_ACTIVE')) {
            return '';
        }
        $this->smarty->assign('mymodule_text', Configuration::get('MYMODULE_TEXT'));
        return $this->display(__FILE__, 'views/templates/hook/footer.tpl');
    }
}
```

> **[TIP] Autoload your classes**
>
> Place your custom classes under `mymodule/classes/` and add `require_once dirname(__FILE__).'/classes/MyClass.php';` at the top of the main file, or register a PSR-4 autoloader in `composer.json` so PrestaShop's autoloader picks them up automatically.

- [ ] **Security header** — `if (!defined('_PS_VERSION_')) { exit; }` at the top of every PHP file
- [ ] **$name matches folder** — the class name, file name and folder name must all be identical (case-sensitive on Linux)
- [ ] **parent::__construct() before displayName** — `$this->l()` is not available until after the parent call
- [ ] **install() chains with &&** — return false at the first failure to trigger rollback
- [ ] **pSQL() on every user input** — sanitise values before writing to the database
- [ ] **deleteByName in uninstall()** — clean up all Configuration entries to leave no orphan data


---

*Fuente: [https://ayudaprestashop.es/modules/main-file](https://ayudaprestashop.es/modules/main-file). Version Markdown generada automaticamente para consumo por LLMs.*
