PrestaShop compatibility: runtimes, APIs and module migration

Build and test modules across PrestaShop versions. Check PHP ranges, removed APIs, Symfony controllers, hooks, themes and upgrade paths.

mypresta.rocks · Updated 11/01/2026

Build one module codebase around the APIs that actually differ between PrestaShop releases. Keep runtime requirements, compatibility helpers and installed-shop testing aligned before you claim support. A successful installation is only the first check: permissions, upgrades, payments, email and integrations need their own evidence on the exact release, PHP runtime and theme.

What we would do

How much work a module needs depends on what it does in the shop and where it runs: the target release, the older shops it still supports, the theme, the integrations it talks to and any custom changes around it. We would inventory the module's dependencies and business responsibilities, then test the highest-risk workflows on a protected copy of the intended shop. Keep a maintained, compatible existing module where it passes. Adapt bespoke code where the requirement remains useful, and replace it only when the replacement covers the actual workflow and its data.

For a merchant moving the whole shop, module compatibility is one part of a wider route. The route depends on the existing shop's modules, integrations, theme and data. The usual safe route is a clean target shop on staging plus a rehearsed data migration. Updating a staging copy is another option where the existing setup supports it. Test the assembled module stack with the shop's own data, then change over at a planned time. The upgrade and migration checklist covers that shop plan. This guide gives the developer the concrete work and the acceptance evidence to supply.

If you want the whole move done for you, the PrestaShop Upgrade Service starts with a free check of your version, PHP, hosting, theme and modules, gives a fixed written quote before work starts and works on a staging copy while the live shop keeps taking orders.

If a requirement needs bespoke module work, the PrestaShop Custom Module Development Service starts with a written specification and a fixed quote for the agreed scope, with testing, a controlled launch and a return plan included in delivery.

Start with the module's business risks

Order the compatibility work by what would stop the business first, not by which code looks oldest.

Module responsibilityInspect firstEvidence required
Checkout, payment or deliveryCart totals, payment lifecycle, callbacks, carrier selection and order creation.Guest and account journeys, discounts, failure and cancellation, callbacks and fulfillment records agree.
Back-office toolsController dependencies, routes, permissions, forms, AJAX and bulk operations.Authorized actions succeed, restricted employee accounts are denied, and filters, exports and writes work.
Front-office displayHooks, presented data, template changes, selectors and assets.Correct output and interaction on supported themes, phone and desktop, with no browser errors.
ERP, feeds or scheduled workAPI contract, credentials, shop context, execution environment and retries.Realistic fixtures reach the right external records without duplicate business operations.
Email and notificationsMail API, transport configuration, templates, recipients and attachments.Representative messages reach a test inbox with the right content and attachments.

List the custom code and module-owned data behind each responsibility. A replacement must preserve the needed behavior and provide a migration route for that data.

For example, an administration tool may open its dashboard correctly but fail when a warehouse employee exports a filtered list or runs a bulk action. A carrier module may show its settings yet fail only when requesting a label. Put those actions ahead of cosmetic controller refactoring, and identify any sandbox or provider access needed to exercise them.

Keep compatibility decisions in one place

A compatibility layer gives your module a small, stable interface for APIs that differ between PrestaShop releases. Keep the version checks there instead of repeating them in controllers, hooks and templates. Test each module against its own declared support range.

BoundaryWhat to keep behind the helper
AJAX responsesJSON encoding, content type, native response rendering and request termination.
Module templatesNative template lookup and rendering, including legacy module resources.
TranslationModern translation calls and legacy module translation fallbacks.
Front-office injectionThe hook available for the required placement, or an explicit unavailable result.
Price formattingLegacy formatting where available and locale-aware formatting on modern releases.
AssetsModern asset registration and older addCSS/addJS calls.

For example, MyPrestaRocks\Compat\PriceFormatter::format() delegates to ToolsCompat::displayPrice(). That helper checks for the native API, resolves the currency and falls back to the current locale formatter.

Some capabilities cannot be recreated by renaming a method. On PrestaShop 1.6, a final-document output-filter hook is unavailable. A body-start fallback through displayBanner also depends on the theme's placement. Report that limitation and use a deliberate template change when the feature needs an exact insertion point.

Separate the PHP syntax floor from the shop runtime

A library's PHP constraint does not define which PHP version can run the shop. Shared code may retain PHP 7.1-compatible syntax while a particular PrestaShop release requires a newer runtime. PHP 7.1 is end of life and is not a hosting recommendation.

TargetPHP compatibilityWhat to record
PrestaShop 1.6 and 1.7Use the requirements for the exact historical release.Exact shop, module and PHP versions. Legacy support does not make an obsolete runtime secure.
PrestaShop 8.0 to 8.2Official range is PHP 7.2.5 to 8.1; PHP 8.1 is the documented recommended target.Use PHP 8.1 for an 8.2 compatibility test. PHP 8.2 is outside this official range.
PrestaShop 9.0PHP 8.1 to 8.4.Test the supported lower and upper runtime boundaries relevant to your module.
PrestaShop 9.1PHP 8.1 to 8.5.Include Hummingbird and the installed feature flags.
PrestaShop 9.2Check the requirements of the exact target build.Include its checkout and service-configuration changes in your validation.

Use the PrestaShop 8 requirements and PrestaShop 9 requirements when building your matrix. A successful experimental run outside a documented range is not the same as official platform support. PHP 8.1 reached upstream end of life on 31 December 2025. Keep an 8.1 environment for compatibility testing where needed, and plan a shop/runtime upgrade that also has current PHP security support.

Record the exact patch release, PHP version, module build, theme and relevant dependencies for each test environment, with the date tested. One passing PrestaShop 9 build is not evidence for every later minor or patch release. Test the lowest and highest PHP versions you claim only where that PrestaShop release supports them, and do not combine independently supported ranges into an invalid pair.

PHP parses a file before it executes a version branch. A condition around a PHP 8-only expression cannot make that file load on PHP 7.1. Keep typed properties, named arguments, enums, match and other newer syntax out of files loaded on older runtimes, or load a separate implementation only on the matching runtime. If one package cannot safely cover every runtime you support, ship separate compatible builds rather than silently dropping existing shops.

Resolve Composer dependencies against the lowest PHP version you advertise. Run composer check-platform-reqs on the installed target as well: a simulated platform is a dependency-resolution setting, not a runtime test.

{
  "require": {"php": ">=8.1 <8.6"},
  "config": {"platform": {"php": "8.1.0"}}
}

This example describes a PHP 8.1 to 8.5 module dependency policy. It does not claim that every PrestaShop release supports all those versions. Keep the module's ps_versions_compliancy, Composer constraints, dependency versions and tested combinations consistent. A ceiling such as 9.99.99 also admits future untested 9.x releases.

Use version checks and capability checks together

Use version_compare() for a known change in behavior. Use method_exists(), is_callable() or class_exists() when you need to know whether an API is available. The existence of a method does not prove identical behavior across releases, and a version number does not prove that a customized environment exposes every expected service.

This asset example shows a capability check inside an actionFrontControllerSetMedia handler. Keep a separate legacy registration path if you also support a release that does not execute that hook.

public function hookActionFrontControllerSetMedia($params)
{
    $controller = $this->context->controller;
    if (!isset($controller->php_self) || $controller->php_self !== 'product') {
        return;
    }

    if (is_callable(array($controller, 'registerStylesheet'))) {
        $controller->registerStylesheet(
            'mymodule-front',
            'modules/' . $this->name . '/views/css/front.css',
            array('media' => 'all', 'priority' => 150)
        );
    } elseif (is_callable(array($controller, 'addCSS'))) {
        $controller->addCSS($this->_path . 'views/css/front.css');
    }
}

Centralize the boundary rather than repeating that branch at every call site. Make an unsupported capability visible to the caller instead of silently reporting success. Preserve the behavior that matters, including locale, currency, shop context and response termination.

Classify each finding before changing code

For each suspected incompatibility, record the symbol or behavior, the exact affected release, the evidence from its source or developer documentation, and the workflow that reproduces it. The classification decides the work:

FindingMeaning for the work
Removal in the selected targetThe module uses an API, hook or dependency that is absent. Replace or explicitly supply it and test the affected path.
Deprecation in the selected targetThe existing path remains available under the stated conditions. Record the warning and migration plan without describing it as already removed.
Recommended design changeInjection, a supported hook or a routed controller may improve maintenance. Establish whether the existing implementation actually fails before expanding the rewrite.

Search for old price-formatting calls such as Tools::displayPrice(), controller base classes and $this->get() service access, security annotations, Context::getContext(), legacy login and product-page hooks, mail libraries and direct dependency imports. These are inspection candidates, not a list of removals: the sections below show that some are removed, some deprecated and some still supported. Check each against the exact target, and do not use a broad search-and-replace as proof that the replacement behaves the same.

Write each finding so a developer can resolve it: the file and symbol, exact target, classification, source evidence, affected journey, proposed change and retest. An entry saying only "old controller" is not actionable. An entry saying "the export action cannot resolve its dependency in this target; here is the exception and route" gives the developer a reproducible failure.

A deprecation does not by itself block an upgrade. Record its impact and planned replacement against your support policy, distinguish a warning from an unavailable path, and keep a working supported path unless the release requirements or demonstrated behavior justify replacing it.

Replace removed APIs without changing the public contract

Price formatting

Tools::displayPrice() was deprecated in PrestaShop 1.7.6 and removed in 9.0. In a modern shop request, use the locale with the currency ISO code. In code spanning multiple releases, call your compatibility helper instead.

$context = Context::getContext();
$formatted = $context->getCurrentLocale()->formatPrice(
    19.95,
    $context->currency->iso_code
);

// With the shared compatibility package installed and autoloaded:
$formatted = \MyPrestaRocks\Compat\PriceFormatter::format(
    19.95,
    $context->currency
);

Initialize the required context explicitly in CLI or worker code. A plain fallback formatter cannot reproduce every currency and locale rule.

AJAX responses

Replace removed ajaxDie() calls with the supported controller response path. Legacy ajaxRender() renders content but does not provide the same termination contract by itself. A compatibility response helper can JSON-encode arrays, set the content type, invoke the native renderer and exit. In Symfony controllers, return a JsonResponse.

Dependencies

PrestaShop 9 removed dependencies older modules may have borrowed from core, including Guzzle, SwiftMailer, Tactician Bundle and PEAR Archive_Tar. Search your imports and every delayed execution path, and inspect the packaged module as well as the target's dependency list. A configuration screen can work while the first scheduled HTTP call or email fails.

Old dependencyMigration decision
GuzzleBundle your own compatible dependency or migrate the HTTP client interface and error handling.
SwiftMailerUse the supported shop mail path or migrate message creation and transport to Symfony Mailer.
TacticianReview the command bus integration and handler wiring. A different bus is not a drop-in replacement.
Archive_TarBundle the needed implementation or use a library supporting the actual archive format. ZIP support does not read TAR archives.

For an HTTP-client migration, preserve the base URL, query parameters, authentication, timeouts and response checks. A Symfony HttpClient request can use auth_bearer and decode JSON through toArray(); handle transport, HTTP and decoding exceptions. For a Mailer migration, construct an Email with the sender, recipient, subject, body and attachments, then send it through a configured MailerInterface. Explicitly register or bundle dependencies your module requires. A bundled library can still conflict with a version loaded by core or another module, so check autoloading in the assembled shop.

PrestaShop 9 mail configuration changed with Symfony Mailer. Test the actual SMTP relay, port, encryption and authentication. An old non-off encryption value is mapped to the TLS flag by the mail implementation, so an upgrade does not inevitably break every previously SSL-configured relay. Send each transactional message type to a test inbox and inspect its variables, sender, translations, headers and attachments.

Migrate Symfony controllers and verify permissions

PrestaShop 9 uses Symfony 6.4. New Symfony back-office controllers should extend PrestaShopAdminController and receive dependencies through constructor or action injection. The older FrameworkBundleAdminController remains as a deprecated compatibility bridge: its get() can still resolve public global-container services. Do not treat that bridge as proof that dependency wiring no longer needs attention.

The following PHP 8.1+ example injects a registered core context service and protects a read action. It assumes your module autoloader maps Acme\Compat\ to src/, and that an AdminAcmeCompat tab supplies the intended permission mapping.

<?php
namespace Acme\Compat\Controller;

use PrestaShop\PrestaShop\Core\Context\CurrencyContext;
use PrestaShopBundle\Controller\Admin\PrestaShopAdminController;
use PrestaShopBundle\Security\Attribute\AdminSecurity;
use Symfony\Component\HttpFoundation\Response;

final class CompatibilityController extends PrestaShopAdminController
{
    private CurrencyContext $currencyContext;

    public function __construct(CurrencyContext $currencyContext)
    {
        $this->currencyContext = $currencyContext;
    }

    #[AdminSecurity("is_granted('read', request.get('_legacy_controller'))")]
    public function indexAction(): Response
    {
        return new Response($this->currencyContext->getIsoCode());
    }
}

Register the controller as a service and expose the route. Keep the access-control mapping aligned with the installed tab. Use the appropriate create, update or delete permission for a state-changing action, and add CSRF protection. Successful autowiring on a clean installation does not exercise every shop combination, so test the actual route, service discovery and action arguments in the merchant's module stack.

# config/services.yml
services:
  Acme\Compat\Controller\CompatibilityController:
    public: true
    autowire: true
    autoconfigure: true
    tags: ['controller.service_arguments']

# config/routes.yml
acme_compat_index:
  path: /acme-compat
  methods: [GET]
  defaults:
    _controller: 'Acme\Compat\Controller\CompatibilityController::indexAction'
    _legacy_controller: AdminAcmeCompat

Attributes, annotations and event listeners

Migrate routing, AdminSecurity and DemoRestricted declarations individually. Modern security attributes use PrestaShopBundle\Security\Attribute. The retained AdminSecurity annotation path is still enforced in PrestaShop 9, although deprecated.

Test each route while logged out and with employees who have different permissions. Confirm that read access does not grant writes. Verify demo-mode restrictions and all AJAX routes too. A page rendering successfully is not a permission test.

  1. Open the actual list or form with an authorized employee. Run a filter, export, row action, AJAX operation and bulk action where the module provides them.
  2. Repeat protected operations with an employee who lacks the relevant permission. Confirm that the denial leaves the records unchanged; a hidden button is not evidence.
  3. Test each protection separately on staging: invalid input with a valid CSRF token, then valid input with a missing or invalid token. Also try a disallowed HTTP method. Check the response and confirm that each rejected write leaves records unchanged.

You can keep tagged YAML event listeners or use Symfony's AsEventListener attribute on a registered, autoconfigured service. An attribute does not register an otherwise undiscovered class. If you use getSubscribedServices() during migration, merge the parent subscriptions and declare the required services explicitly.

Supporting two controller generations

If one module supports PS 8 and 9, a small compatibility parent can choose the available class. This only chooses inheritance; it does not normalize every method or service-container difference.

<?php
namespace Acme\Compat\Controller;

if (class_exists('PrestaShopBundle\Controller\Admin\PrestaShopAdminController')) {
    abstract class CompatibleAdminController extends \PrestaShopBundle\Controller\Admin\PrestaShopAdminController
    {
    }
} else {
    abstract class CompatibleAdminController extends \PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController
    {
    }
}

Wire services, context and console commands for the target release

Service configuration

On releases before 9.2, retain config/services.yml as the module service entry point. It can explicitly import PHP configuration. Keeping a standalone services.php beside it does not make older loaders select that file automatically.

# config/services.yml on an older target
imports:
  - { resource: services.php }

In 9.2, the service-file priority is services.php, then services-9.2.yml, then services-9.yml, then services.yml. Treat the selected file as the entry point and import anything else it needs. Autowiring still needs explicit values for scalar arguments and explicit bindings when interfaces have multiple implementations.

Working YAML does not need rewriting into PHP for appearance: keep it while the target loads it and the required services resolve. Changing the format alone is not acceptance evidence.

Review service decoration with the complete installed module stack. Decoration order, service visibility and another module's wiring can change which implementation receives a call.

Context and employee authentication

Context::getContext() remains available. Prefer injected context services for new Symfony code, and establish the shop, language, currency and employee context needed by commands or workers.

Context services include EmployeeContext, ShopContext, LanguageContext, CurrencyContext, CountryContext, ApiClientContext and LegacyControllerContext. Use CurrencyContext::getId(), getIsoCode() or getConversionRate() for the value you need. There is no CurrencyContext::getCurrency().

The back office uses Symfony Security for employee authentication. Read employee identity through the supported employee context or security service instead of treating legacy cookie fields as the authentication authority. EmployeeContext::getEmployee() can return null, and injecting a context service does not log an employee in for a command or worker. Use RequestStack for your own request-session data and handle a missing request or session outside HTTP execution.

Console kernels

PrestaShop 9 has admin, admin-api and front application kernels. Inspect the installed console's command list and help for the exact release, confirming its application identifiers and supported options before using the examples below. Check your command and services in the kernel where they run. Do not assume a command registered in one application is available in another.

php bin/console list --app-id=admin
php bin/console debug:container 'Acme\Compat\Controller\CompatibilityController' --app-id=admin
php bin/console debug:event-dispatcher kernel.request --app-id=front
php bin/console debug:router --app-id=admin-api

Use list, debug:container and the service configuration to diagnose discovery. Register console commands with the supported Symfony configuration for the target kernel. Inspect the command's service definition and tags in that kernel, and run scheduled commands with the CLI PHP binary and execution account the scheduler actually uses.

Example: the settings page works, the scheduled export fails

Suppose the settings page saves correctly, but the export first loads an HTTP library when a scheduled job runs. Test that command from the delivered package with the intended CLI PHP and execution account, and inspect the library's availability, configuration and the external response. A successful installation never exercised that path.

Then compare the command's shop and language context with the administration request. A worker must not silently inherit whichever shop an employee last selected. Confirm that it reads the intended records and writes to the intended remote account, then retry a failed export in the sandbox and check for duplicate external records.

Check hooks, theme data and routed endpoints

Hooks and page migrations

The hook system remains available, but each executed hook belongs to a particular code path. The migrated product editor no longer executes the old actionAdminProductsController* lifecycle hooks. Some legacy login hooks remain: actionAdminLoginControllerSetMedia for assets and displayAdminLogin for HTML. Use actionBackOfficeLoginForm and actionEmployeeRequestPasswordResetForm to extend those forms, Symfony Security events for authentication logic, and modern product form hooks for product fields.

For front pages using presenters, read the presented data rather than assuming an old object shape. Image data may now be an array containing size-specific URLs, such as $manufacturer.image.medium.url in Smarty. Prefer the matching actionPresent* hook for data changes where available.

Smarty, Twig and Hummingbird

PrestaShop uses both template systems. Front-office themes and legacy back-office pages use Smarty. Migrated Symfony back-office pages use Twig. The move to Symfony did not replace Smarty throughout the platform.

Hummingbird is the default theme for fresh 9.1 installations; an upgrade does not automatically replace the shop's existing theme. Test Classic, Hummingbird and each theme you advertise, and the merchant's actual theme. Check product cards, combination changes, cart, checkout, customer account, hook locations, selectors and JavaScript events. Theme-specific markup assumptions can fail even when the PHP APIs still work: a hook may render the right text while a theme's different markup stops a selector or click handler from finding the control. Keep browser errors visible on protected staging.

AJAX and callbacks

Move directly callable module-root ajax.php and callback.php scripts into the supported controller lifecycle. Until you do, do not assume such a file either fails on PrestaShop 9 or is safe: inspect its authentication, context and request handling. A ModuleFrontController gives the request a normal routing and context path. It does not automatically authorize your business operation.

For a legacy AJAX controller exposing displayAjaxProcess(), activate AJAX dispatch as well as choosing an action. A generated module URL can carry ajax=1 and action=process. On PrestaShop 9, an Accept: application/json request also activates AJAX detection. The presence of action=process alone is insufficient.

$url = $this->context->link->getModuleLink(
    $this->name,
    'ajax',
    array('ajax' => 1, 'action' => 'process'),
    true
);

Require customer authentication and ownership checks for customer data. Require employee permissions and CSRF protection for admin actions. Validate the provider's signature for external callbacks and apply its duplicate-handling rules; changing the URL does not establish trust. Keep token or signature checks on the server, use appropriate HTTP methods and return the intended status and content type.

Test provider callbacks through the provider's sandbox tools: a missing or invalid verification, the normal valid event and a repeated delivery. A rejected event must not perform the business operation, and a repeat must not duplicate it.

During an endpoint migration, coordinate the new address with the external service and test it before cutover. Do not leave the older address available as an undocumented fallback. Agree how delayed events reach the correct endpoint during deployment, and preserve them for reconciliation without letting them change a partially migrated database.

Integration contracts that remain available

The legacy Webservice API remains alongside the newer Admin API. Choose the API by its supported resources and authentication contract rather than replacing an integration by name alone, and verify permissions, payloads, identifiers and retry behavior before changing one. The module descriptor, Db::getInstance(), Configuration::get(), Configuration::updateValue() and ObjectModel remain available. Specific deprecated classes or methods need individual migrations; core entity ObjectModels as a group are not deprecated.

See the hooks guide, Webservice API guide and security checklist for the wider extension, integration and hardening workflow.

Control conflicts and store-specific behavior

Prefer hooks, supported services and service decoration when they provide the required extension point. Avoid editing core files. Direct core changes make it harder to compare behavior, apply updates and reproduce a fault.

A class override normally extends the installed Core class. Its inherited methods still come from that core. Copied logic inside an overridden method can become stale, and an incompatible signature can fail after an upgrade.

The module installer can merge non-conflicting methods, properties and constants into an existing class override. Two modules extending Product are not automatically incompatible. Conflicts arise when they define the same member or introduce incompatible behavior. Document the affected members and check the assembled override and class-index cache after changes.

Exceptions need to be visible: a module can ship a justified PDF-class override, for example. Ask what it changes, how it interacts with other installed modules, how upgrades are tested and what uninstall removes. Uninstall may intentionally preserve data, and it cannot promise to undo every external effect.

Test the environment as well as the version number:

  • Apache, Nginx or LiteSpeed rewrite behavior and file permissions.
  • Required PHP extensions, memory limits, OPcache and background-job PHP versions.
  • Single-shop, shop-group and all-shops contexts, including configuration inheritance.
  • Customer groups, group prices, specific prices, taxes, currencies, languages and rounding.
  • Other modules using the same hooks, services, templates or class members.
  • Modified core files, custom themes and historical data from earlier module releases.

Validate incoming hook data and unexpected state. Keep translations in the appropriate API. When a capability is absent, explain which function is unavailable and how to proceed. Clear labels, defaults and help text reduce configuration errors.

Keep compatibility code cheap and maintainable

Exit early from hooks when the current page does not need your feature. Scope stylesheets and JavaScript to the controllers that use them. A header hook can execute across many front-office pages, so avoid an unnecessary query or template render on every request.

Cache reusable query results only when the key includes every relevant shop, language, currency and customer context. Invalidate them when the underlying data changes. Request-local caching and persistent caching have different lifetimes.

Measure overhead on representative data with a profiler. On staging, _PS_DEBUG_PROFILING_ can help alongside development mode. Compare the same route, cache state and data before and after the change. There is no universal module millisecond allowance that proves a store will be fast.

Keep the wrapper smaller than the problem it solves. Use clear methods, ordinary control flow and one well-defined fallback. Shared fixes should reach every consuming module through a controlled dependency update and its release checks. Remove unused features deliberately, and turn recurring support questions into better documentation or reproducible test cases.

Test the installed ZIP and every advertised upgrade path

Start with a clean installation for each supported platform combination, using the delivered ZIP rather than the developer's working copy: dependencies present only on the developer's machine do not show that the merchant receives a complete module. Then use a staging copy shaped like the real shop: representative catalog size, order history, languages, theme and module stack. Protect customer data and keep test payment, email and integration credentials separate. Isolate the copy before its first application, worker or scheduled-task run, with a separate database, sandbox credentials, a test inbox and controlled external access, so tests cannot contact real customers or fulfill real orders.

AreaRun these checks
Module lifecycleInstall the release ZIP, enable, disable, reset where supported, upgrade from advertised older builds and uninstall. Inspect tables, hooks, tabs, defaults, permissions and retained data.
Modern adminResolve services and routes; test each action with debug mode on and with restricted employee permissions.
Legacy adminList filters, row actions, bulk actions, modals, exports, token URLs and AJAX responses.
Front officeProduct and listing output, hooks, responsive layout, translations, account pages and browser-console errors on each supported theme.
CheckoutGuest and account orders, discounts, tax/group pricing, payment callbacks and all supported carrier flows. Include the 9.2 one-page checkout when targeting it.
IntegrationsWebservice or Admin API authentication, exports, jobs, retries, timeouts and error responses.
EmailEvery message path, relay settings, templates, recipients, headers and attachments.
DiagnosticsPHP warnings, deprecations, application errors, slow queries, container errors and logs hidden in production mode.

Keep PS 9.0 or 9.1 in the matrix for as long as you advertise them. Add later releases explicitly. In 9.1, review the installed discount and multi-shipment feature flags, lifecycle/configuration hooks and CLI operations. In 9.2, include service-file selection and the one-page checkout contract. Use the 9.0 changes, 9.1 changes and 9.2 changes to identify release-specific checks.

  1. Inventory the module's controllers, dependencies, hooks, jobs, endpoints and template assumptions.
  2. Fix removed APIs and dependency ownership, then migrate controller services and access controls.
  3. Verify both fresh-install and module-upgrade results. Do not assume they reach the same state.
  4. Exercise the delayed code paths, including scheduled jobs, bulk actions and emails.
  5. Repeat the relevant flow with the actual theme and other installed modules.
  6. Publish exact tested versions, limitations and rollback instructions with the release.

Make each test reproducible

Use the same worksheet for the merchant's target and for the older environments still supported. Record these fields in the test report:

  • Environment: exact PrestaShop and PHP versions, theme, shop context, relevant dependencies and date.
  • Package: module version and the exact distributable tested, with the starting module version for an upgrade.
  • Starting data: representative saved settings and records, languages, currencies and employee role.
  • Action and expectation: the customer or staff task and its expected business result.
  • Observation: pass or failure, related logs or screenshots, known limitation and person responsible.

Example: keep merchant settings during a module upgrade

Use a staging fixture with merchant-edited settings, module records and the relevant shop associations. Save the important values before running the supported upgrade. Afterwards, compare the intended schema, hooks, tabs and permissions with the target design, and confirm that existing choices and linked records survived. A fresh installation can establish new defaults; an upgrade should not overwrite a merchant's valid choices merely to match those defaults.

Do not reset or uninstall a module to make an upgrade test pass: that can remove the very data the upgrade must preserve.

Deploy with a return plan

Do not replace a full test run with a wide compatibility declaration. Keep an installable previous module build and a matching database/files backup before deployment. Before schema changes, removal or replacement, restore that backup to staging and rehearse the rollback, then verify the required customer and staff journeys after restoring as well as after upgrading.

Agree the deployment window, the rollback trigger and how to reconcile orders or external writes accepted after the snapshot. Deploy the tested package, repeat the essential checks, watch the relevant scheduled and business cycles and confirm public debug output is off. Hand over the package, source, dependency and configuration notes, test results and recovery procedure.

What to ask the developer before signing off

  • Which exact environments and existing-data upgrades passed, and which remain untested?
  • Which findings were genuine incompatibilities, and which changes were optional maintenance?
  • Which customer settings and module records were compared before and after the upgrade?
  • Can another developer build and deploy the delivered package from its documented source?
  • Who handles provider coordination, deployment failure and reconciliation after rollback?

If a change fails, preserve the evidence and use the troubleshooting guide to isolate the failing path before applying more migrations or another live change.

Loading...
Back to top