Every module we ship runs on PrestaShop 9. Here's what that took.

We started rebuilding our catalogue against PrestaShop 9 alphas in early 2024. By the time 9.0 stable shipped, our internal CI was already running every module ZIP through eight PrestaShop versions and four PHP versions before we'd merge a single line. Today every module in our catalogue of 151 installs and runs on PS 9.0 and 9.1 from the same ZIP that installs on 8.x and 1.7.6+. One codebase, runtime version detection, no "PrestaShop 9 edition" pricing.

Software compatibility testing and module verification for PrestaShop 9

That's the headline. The rest of this post is the part most "we support PS 9!" announcements skip: what actually broke during the migration, what we caught only because we test on real shops, and what you should be asking other vendors before you trust their compatibility claim.

What PrestaShop 9 actually changed

If you've only read the PrestaShop blog summary, you'd think PS 9 is a tidy Symfony upgrade. It isn't. It's the most disruptive release since 1.7 introduced Symfony in the first place, and the disruption is concentrated in the parts of the framework that modules touch most.

Symfony 4.4 to 6.4 — two majors in one jump

This is where most third-party modules fall over. In 4.4 you could write $this->get('service_name') inside an admin controller and grab anything from the container. In 6.4 that pattern still works (PrestaShop kept a compatibility layer) but the base class it relies on, FrameworkBundleAdminController, is officially deprecated and scheduled for removal in PrestaShop 10.

Translation: if a module's admin pages render today on PS 9 but it still extends FrameworkBundleAdminController, that module has a known expiry date. We rewrote every one of our admin controllers to extend PrestaShopAdminController with proper constructor injection. It's the slowest part of the migration and it's the part you can't skip.

Libraries the core no longer hands you

PrestaShop 9 removed three libraries that modules used to get for free from the core's vendor/:

  • Guzzle — replaced by Symfony's HTTP client. Any module that use GuzzleHttp\Client and doesn't ship its own copy throws a class-not-found fatal the moment it tries to make an HTTP call.
  • SwiftMailer — replaced by Symfony Mailer. Modules sending mail directly through Swift will not just throw a deprecation; they'll fatal.
  • Tactician command bus — replaced by Symfony Messenger. If a module used CQRS via Tactician, the command/query handlers need rewriting.

The bit that catches people: these failures don't show on install. The module loads, the configuration page renders fine, and then it crashes the first time a cron fires or a customer triggers the email-sending code path. We caught two of our own modules this way during PS 9 alpha testing — they installed clean, looked healthy in the back office, and only blew up at 3am when a stock-sync cron ran.

33 hooks removed

That's not a typo and it's not a deprecation warning — they're gone. The removals cluster into three groups:

  • Legacy admin product hooks — the entire old product editor went away and took its hooks with it (actionAdminProductsControllerXxx, actionAdminActivateAfter/Before, actionAdminDeactivateAfter/Before, actionAdminDeleteAfter/Before, actionAdminSortAfter/Before)
  • Admin login hooks — now handled by Symfony security (actionAdminLoginControllerBefore, actionAdminLoginControllerLogin/Forgot/Reset Before/After)
  • AdminController lifecycle hooksinitHeader, initContent, initFooter, display still fire on legacy controllers wrapped by LegacyController, but new Symfony admin pages skip them entirely. If your module relies on these, test on every admin page where it appears.

What this looks like in practice is worse than a crash: a module registers on a removed hook, install succeeds, the hook simply never fires, and the feature silently stops working. We had one of our own modules in this state on a staging shop for two weeks before we noticed an admin-side notification badge had quietly disappeared.

PHP 8.1 minimum, and we test through 8.4

PS 9 drops PHP 8.0 and below. We test every module against 8.1, 8.2, 8.3 and 8.4 because each minor version has its own deprecation set, and a deprecation in 8.3 that just fills logs is often a hard error by 8.4. Catching them on 8.4 in CI is cheaper than discovering them when a hoster bumps a client's shop to 8.4 overnight.

Advanced Stock Management is gone

The whole subsystem (supply orders, warehouses, the ASM admin interface) was removed in PS 9. The database tables are gone too, so modules that queried ps_supply_order* or ps_warehouse* directly will throw SQL errors, not just PHP warnings. Two of our own modules (mprwarehouserevolution and mprstockorder) handle warehouse data independently of ASM precisely because we never trusted ASM to survive — that bet paid off.

Context singleton is being retired

PS 9 introduces dedicated context services: EmployeeContext, ShopContext, CurrencyContext, CountryContext, LanguageContext. Context::getContext() still works and will for a while, but it's a code smell now. We migrate it opportunistically — when we're already in a file fixing something else, we swap to the dedicated service.

trans() no longer escapes HTML

This one is dangerous because it's invisible. In PS 8 and below, the trans() function escaped HTML entities as a side effect. A lot of module code accidentally relied on that for XSS protection without realising it. In PS 9, you must explicitly htmlspecialchars() any dynamic content before passing it to trans(). Modules that don't are now sitting on potential XSS holes that worked fine for years.

Hummingbird, Bootstrap 5, and the jQuery clock

PS 9.1 ships Hummingbird as the default theme for fresh installs, on Bootstrap 5.3.3 (PS 9.0 used Bootstrap 5, upgraded shops keep their existing theme). For front-end-touching modules that means:

  • Bootstrap 4 utility classes are gone (.no-gutters is .g-0, .custom-checkbox is .form-check)
  • Data attributes are namespaced (data-toggle is data-bs-toggle)
  • jQuery is officially deprecated in Hummingbird and on a removal timer. We've already rewritten the front-end JS for our checkout, performance, and product-tabs modules to vanilla — partly to get ahead of the deadline, partly because Hummingbird's checkout layout punishes jQuery's DOM-thrash patterns
  • PrestaShop-specific class selectors are being replaced with data-ps-* attributes — if your CSS or JS targets .cart-block, expect to retarget it

How to audit your existing module stack

Most shops we migrate aren't running only our modules — they're running a stack of fifteen to forty modules from a dozen vendors, and our modules are five of them. Here's the process we run on every client migration.

Step 1: Inventory honestly

Modules → Module Manager. Export the list. For each module, write down: name, current version, vendor, last update date, and how critical it is. A payment gateway is critical; a social-share widget is decoration. The criticality ranking is the one that drives your timeline, not the alphabetical order.

Step 2: Read the vendor's changelog, not their headline

"PrestaShop 9 compatible" on a homepage means nothing. What you want is a changelog entry dated after PS 9's public release that explicitly mentions Symfony 6.4 work, PHP 8.1+ testing, or removed-hook replacement. If the vendor's most recent update is from 2023 and the marketing copy still leads with "PrestaShop 1.7 compatible," the module is probably abandoned and the website hasn't caught up yet.

Step 3: Grep the module code

If you have a developer (or your own command line), unzip the module and grep for the patterns that are guaranteed problems:

# Legacy Symfony 4.4 service access
grep -rn '\$this->get(' modules/yourmodule/

# Removed core libraries
grep -rn 'use GuzzleHttp' modules/yourmodule/
grep -rn 'Swift_\|SwiftMailer' modules/yourmodule/
grep -rn 'League\Tactician' modules/yourmodule/

# Deprecated price formatting (removed in PS 9.0)
grep -rn 'Tools::displayPrice' modules/yourmodule/

# Bootstrap 4 data attributes
grep -rn 'data-toggle=' modules/yourmodule/views/templates/

# jQuery usage in front-end JS
grep -rn '\$.ajax\|\$(document)' modules/yourmodule/views/js/

A hit on use GuzzleHttp without a composer.json shipping Guzzle in the module's own vendor folder is a confirmed crash on PS 9. A hit on Tools::displayPrice is a hard fatal — that method was removed, not just deprecated. We maintain a small prestashop-compat package internally that wraps these for our modules; for third-party modules without that protection, the call dies.

Step 4: Staging that actually mirrors production

Never test compatibility on the live store. Our minimum staging recipe:

  1. Clone the production database into a separate DB (most decent hosters have a one-click for this)
  2. Install PS 9.1 on it — go straight to 9.1, not via 9.0, since 9.1 is where you'll end up anyway
  3. Install modules in dependency order: payment and shipping first, then SEO/marketing, then cosmetic. This way a crash from a heavy module doesn't block your ability to test the lighter ones
  4. After each install, tail the PHP error log and the PrestaShop log under var/logs/. Most compatibility issues are deprecation notices first and fatals on the next minor — don't dismiss warnings
  5. Test the module's actual job, not just "does the config page open." Place a real test order. Run an import. Generate a real sitemap. The config page rendering proves almost nothing

The single highest-yield habit we've adopted: tail -f var/logs/* in one terminal while clicking around the back office in another. Deprecation noise is the leading indicator of next-version breakage. If a module is filling that log on PS 9.1, it's the module that'll fatal on 9.2.

Step 5: Front-end smoke test on the theme you'll actually run

If you're switching to Hummingbird, every module with front-end output needs a visual check on the real theme — not on Classic in a test admin. The places we always find breakage:

  • Checkout — payment forms, carrier blocks, address validation, anything injected into the order summary
  • Product pages — reviews, wishlists, size guides, custom field blocks
  • Header/footer — mega menus and search dropdowns are especially fragile on the Bootstrap 5 migration
  • Category page filters and sort controls

If you're staying on Classic, your risk is lower — but it isn't zero, because Bootstrap 5 still lands in the admin and any module sharing CSS between front and back can still misbehave.

The questions worth asking a vendor

"Is your module PrestaShop 9 compatible?" is a yes/no question that returns "yes" 100% of the time, often incorrectly. These are the ones we ask vendors when we're vetting their modules for a client stack:

  1. Is the module tested on 9.0 and 9.1? 9.1 added Bootstrap 5.3.3, killed displaySearch, and reworked the data-ps-* selector convention. Testing only on 9.0 is half a job.
  2. Is it the same ZIP for 8.x and 9.x? If you need a separate download for PS 9, that's a maintenance trap waiting to happen on every shop running multiple versions. Single-ZIP modules with runtime version detection are how this should work.
  3. Have you migrated off FrameworkBundleAdminController? If the developer doesn't recognise the question, you've learned something important.
  4. Does the front-end JS still need jQuery? Fine for now, but ask for the migration timeline. Hummingbird will drop jQuery and you don't want to be a vendor's last priority when that happens.
  5. What's the PHP testing matrix? The answer you want is "8.1 through 8.4," not "PHP 8."

When to upgrade, when to wait

Go now if your critical modules have explicit PS 9.1 compatibility, you're already on PHP 8.1+, you have a working staging copy, and your theme is Classic, Hummingbird, or a child of one of them.

Wait three to six months if one or two nice-to-have modules don't have compatibility confirmation, your theme is a heavily-customised paid theme (theme vendors lag — we've seen 18-month gaps), or you rely on Advanced Stock Management and haven't decided on a replacement yet.

Don't upgrade if a critical module (payment, shipping, ERP sync) has no PS 9 statement and the vendor hasn't shipped anything for a year, you're still on PHP 7.4 or 8.0 (that's a separate project — do it first), or your shop runs heavy customisations from an agency that hasn't tested PS 9.

How we actually got our 151 modules onto PS 9

Worth describing in detail because "we support PrestaShop 9" means different things to different vendors, and the difference matters when you're trusting someone's module on your live shop.

Module quality assurance testing ensuring PrestaShop 9 compatibility

Phase 1: Static analysis across the whole catalogue

We ran a custom scanner over every module's source looking for the patterns above: legacy controller usage, Guzzle/Swift/Tactician imports, Tools::displayPrice, Bootstrap 4 markup, jQuery dependencies. The output was a per-module work list ordered by risk. We didn't trust ourselves to "remember" which modules needed which fix.

Phase 2: Symfony 6.4 controller migration

Every admin controller that touched the service container moved from $this->get() to constructor injection. Every controller extending the deprecated base class was migrated to PrestaShopAdminController. This is the slow phase — it touches the spine of every admin-facing module and there's no shortcut.

Phase 3: Hook audit and replacement

We cross-referenced every registerHook() call across the catalogue against PS 9's removed-hook list. Where a hook was gone, we either migrated to the replacement or added version-conditional registration in the install scripts — so a module installing on PS 8.x still uses the old hook, and the same ZIP on PS 9 uses the new one.

Phase 4: Shared compat layer

The methods most likely to be removed in future minors live behind wrappers in a shared package (prestashop-compat). Tools::displayPrice is the obvious one — it's now \MyPrestaRocks\Compat\PriceFormatter::format() across all 151 modules, and the wrapper detects the PS version and calls the right API. When PS 9.2 removes something else, we update the wrapper once, sync, and every module is covered.

Phase 5: Matrix testing in CI

Every module ZIP is tested across PrestaShop 1.7.6, 1.7.7, 1.7.8, 8.0, 8.1, 9.0, and 9.1 — and on each version against PHP 8.1, 8.2, 8.3, and 8.4. We run this on dedicated Docker containers (ps176-dev, ps178-dev, ps8-dev, ps9-dev) on our own infrastructure, spinning up temporary containers for specific minor versions when needed. A single module ZIP works on every combination because the module branches on _PS_VERSION_ at runtime, not because we ship multiple builds.

Phase 6: Hummingbird and Classic theme verification

Every module with front-end output is rendered on both themes before release. Where templates needed to differ (and a few did, mostly around data-bs-* attributes and form components) we use PrestaShop's version helper to pick the right template. The merchant doesn't see this; their views/templates/hook/*.tpl just works on whichever theme they have.

Where we stayed honest

A handful of our modules touch areas that PS 9 redesigned at the framework level — back-office product tabs, multi-shop context, AdminAPI bridges. For those, we tested every single feature path on PS 9.0 and 9.1 manually before flipping the compatibility flag. We don't claim compatibility because the category looks safe; we claim it because the module ran end-to-end on the version we're claiming.

The result: every module in the mypresta.rocks catalogue installs and runs on PS 9.0 and 9.1 from the same ZIP that runs on 8.x and 1.7.6+. No separate edition, no migration fee.

What goes wrong after a successful upgrade

Even with vendor-confirmed modules, here's the pattern of post-upgrade issues we see — in roughly the order they happen.

500 error after installing a single module

Almost always a missing Composer dependency in the module's own vendor folder. The PHP error log will say "Class not found" with a recognisable name — usually GuzzleHttp\Client, Swift_Mailer, or a Symfony Messenger class. Either update the module or, if the vendor is slow, drop the missing library into the module's vendor/ manually (we've done this on client shops to keep them running while waiting for a real fix).

Admin page loads, feature is silently dead

The removed-hook problem. The module installed, the configuration page renders, but the hook the feature relied on never fires. Check the module's install() against the PS 9 removed-hook list. The fix is a vendor update — no client-side workaround makes a removed hook come back.

Front-end looks broken on Hummingbird, fine on Classic

Bootstrap 4-to-5 class drift. The module's templates use .no-gutters, .custom-checkbox, data-toggle, or other BS4 markup. Temporary workaround: switch the shop to Classic theme while you wait. Real fix: vendor template update.

JavaScript errors in the console

Either jQuery is being expected and isn't loaded, or selectors are targeting old class names that have moved to data-ps-*. $ is not defined in the console is the giveaway. Short-term you can load jQuery manually in the theme; long-term the module needs vanilla JS.

Emails just stop sending

Module is invoking SwiftMailer directly instead of going through PrestaShop's Mail::Send(). PS 9 has no SwiftMailer to instantiate. There's no client-side fix; you need the module updated to use Symfony Mailer.

PrestaShop 9.1 — the extras you need to know

If you're skipping 9.0 and going straight to 9.1 (sensible — it's where you'll end up regardless), 9.1 added its own breaking changes on top of 9.0:

  • Theme::getDefaultTheme() no longer hard-codes "classic." Modules that assumed Classic-is-default for fallback logic will misbehave.
  • The displaySearch hook is gone from Hummingbird — it was causing rendering conflicts on 404 pages. Modules hooking displaySearch for front-end search customisation need a different hook.
  • D3 and NVD3 versions were bumped. If a dashboard widget was tied to a specific D3 API, it can render wrong on 9.1.
  • JavaScript selectors moved from class-based to data-ps-*. Any querySelector('.something') against PrestaShop's own DOM may stop matching.

The pre-upgrade checklist we use on client shops

  1. Inventory every installed module with vendor and version
  2. Check every vendor's changelog for an explicit 9.0/9.1 entry dated after PS 9 release
  3. Update every module to its latest version before upgrading PrestaShop itself
  4. Provision a staging environment with a fresh copy of the production database
  5. Install PS 9.1 on staging — straight to 9.1, not via 9.0
  6. Install modules in dependency order, tailing logs after each
  7. Run the actual functionality of every module — orders, imports, exports, sitemaps, emails
  8. Render the front end on the theme you'll use in production
  9. Place a full real test order through checkout, including payment confirmation
  10. Read the PHP error log for deprecation warnings — they're future fatals
  11. Verify every module that sends mail actually sent its mail
  12. Take a full backup (database, files, configuration) and keep it for 30+ days post-upgrade
  13. Schedule the live upgrade for the lowest-traffic window your shop has
  14. Keep the 8.x backup handy for at least a month. Rollback should be possible until you're certain

Closing

PS 9 is a real improvement once you're on it — Symfony 6.4 is genuinely faster, Hummingbird is genuinely lighter than Classic, and the AdminAPI opens integration patterns that the legacy Webservice can't reach. None of that helps you if the upgrade breaks the modules your shop runs on.

The migration is doable. It's not a weekend job, and "compatible" is a word you should make vendors back up with specifics. We did the work across our catalogue, on our own infrastructure, against the actual PrestaShop versions we claim — because every shop running our modules eventually upgrades, and we'd rather do the work once than answer support tickets forever.

If you want modules where the PS 9 work is already done (same ZIP, all PS versions, no upgrade fee) start with the mypresta.rocks catalogue. If you want a hand evaluating your existing stack, drop us a line.

Share this post:
David Miller

David Miller

Over a decade of hands-on PrestaShop expertise. David builds high-performance e-commerce modules focused on SEO, checkout optimization, and store management. Passionate about clean code and measurable results.

Enjoyed this article?

Get our latest tips, guides and module updates delivered to your inbox.

Comments

No comments yet. Be the first!

Be the first to ask a question or share useful feedback.

Loading...
Back to top