Repertoire /themes/ PrestaShop en profondeur : Comment fonctionnent les templates, assets et child themes
This is Part 2 of the PrestaShop Architecture Series. Part 1 covers the full folder structure overview.
Ce que controle le repertoire /themes/
Every pixel of your store's front office passes through the /themes/ directory. When a customer visits a product page, PrestaShop does not render HTML from PHP directly -- it loads a Smarty template from your active theme, fills it with data from controllers and hooks, compiles it, and sends the result. The theme controls the HTML structure, the CSS styling, the JavaScript behavior, and even which hooks are displayed where.
Understanding how this directory works means understanding how PrestaShop builds every page your customers see.
Structure des repertoires d''un theme
themes/
classic/ <-- PS 1.7/8 default
hummingbird/ <-- PS 9 default
my-child-theme/ <-- your customizations
[active-theme]/
config/
theme.yml <-- theme identity, assets, layouts
templates/
catalog/
product.tpl <-- product page
listing/
category.tpl <-- category listing
product-list.tpl <-- product grid/list
checkout/
cart.tpl <-- shopping cart
checkout.tpl <-- checkout flow
order-confirmation.tpl <-- thank-you page
cms/
page.tpl <-- CMS content pages
customer/
my-account.tpl <-- customer dashboard
addresses.tpl <-- address management
history.tpl <-- order history
errors/
404.tpl <-- page not found
maintenance.tpl <-- maintenance mode
_partials/
head.tpl <-- <head> section, meta, CSS
header.tpl <-- site header, navigation
footer.tpl <-- site footer
notifications.tpl <-- success/error messages
breadcrumb.tpl <-- breadcrumb navigation
pagination.tpl <-- list pagination
layouts/
layout-full-width.tpl <-- full-width page layout
layout-left-column.tpl <-- with left sidebar
layout-right-column.tpl <-- with right sidebar
layout-both-columns.tpl <-- both sidebars
assets/
css/
theme.css <-- main compiled stylesheet
custom.css <-- user overrides (if theme supports it)
js/
theme.js <-- main compiled JavaScript
custom.js <-- user scripts
img/
logo.png <-- store logo
favicon.ico <-- browser tab icon
modules/
ps_featuredproducts/
views/templates/hook/
ps_featuredproducts.tpl <-- theme override of module template
translations/
en-US/
Shop/Theme/Global.xlf <-- theme-specific translations
preview.png <-- 200x200 theme preview
Comment PrestaShop resout les templates
When a controller needs to render a page, PrestaShop searches for the template in a specific order. Understanding this order is critical -- it determines which file actually renders your page.
Template resolution order (from highest to lowest priority):
- Child theme --
themes/my-child/templates/catalog/product.tpl - Parent theme --
themes/classic/templates/catalog/product.tpl
Module template resolution (for hooks):
- Child theme module override --
themes/my-child/modules/ps_featuredproducts/views/templates/hook/ps_featuredproducts.tpl - Parent theme module override --
themes/classic/modules/ps_featuredproducts/views/templates/hook/ps_featuredproducts.tpl - Module's own template --
modules/ps_featuredproducts/views/templates/hook/ps_featuredproducts.tpl
This three-level fallback is what makes child themes possible: you only include files you want to change, and everything else falls through to the parent. It is also what makes theme-based module overrides possible: a theme can completely redesign how a module renders without touching the module's own files.
Practical implication you must understand: When a theme overrides a module template, the override takes absolute priority. If the module releases an update that fixes a security issue in its template, your theme's override will still serve the old version. You must manually compare and update theme overrides when modules update. This is the hidden maintenance cost of theme overrides that most documentation glosses over.
Comment une page est rendue : De la requete au HTML
Here is the exact sequence when a customer visits /en/product/linear-drain-800mm:
- Apache/Nginx receives the request, rewrites it via
.htaccesstoindex.php - PrestaShop bootstrap loads
config/config.inc.php, initializes the Dispatcher - Dispatcher parses the URL, identifies the controller:
ProductController - ProductController::init() loads the product data from the database
- ProductController::initContent() assigns Smarty variables (
$product,$combinations, etc.) - Hook execution -- the controller calls
Hook::exec('displayProductAdditionalInfo')and other hooks. Each registered module runs its hook method, which typically assigns its own Smarty variables and returns rendered HTML. - Template selection -- the controller calls
$this->setTemplate('catalog/product'). PrestaShop resolves this to the actual file path using the resolution order above. - Layout wrapping -- the product template is rendered inside a layout template (
layouts/layout-full-width.tpl). The layout includes_partials/head.tpl,_partials/header.tpl, the page content, and_partials/footer.tpl. - Smarty compilation -- Smarty compiles the
.tplfiles into PHP, caches the result invar/cache/prod/smarty/compile/. Subsequent requests skip compilation and use the cached PHP directly. - HTML output -- the compiled template executes, produces HTML, and PrestaShop sends it to the browser with CSS/JS references.
The entire chain typically takes 200-500ms on a well-configured server. Of that, template compilation is 0ms on cached requests (the compiled PHP is reused) and 50-100ms on the first request after a cache clear.
theme.yml: The Theme's Configuration File
The config/theme.yml file defines everything about your theme: its identity, parent relationship, asset registration, layout assignments, and hook configuration.
parent: classic <-- child theme inherits from this
name: my-child-theme <-- directory name (must match)
display_name: My Custom Theme <-- human-readable name
version: 1.0.0
author:
name: "Your Name"
email: "you@example.com"
meta:
compatibility:
from: 1.7.0.0
to: ~ <-- no upper limit
assets:
use_parent_assets: true <-- inherit CSS/JS from parent
css:
all:
- id: custom-styles
path: assets/css/custom.css
media: all
priority: 200 <-- higher = loaded later
js:
all:
- id: custom-scripts
path: assets/js/custom.js
priority: 200
theme_settings:
default_layout: layout-full-width
layouts:
category: layout-left-column
product: layout-full-width
checkout: layout-full-width
Critical fields:
parent-- establishes the inheritance chain. Without this, PrestaShop treats the theme as standalone and won't fall back to any parent templates.use_parent_assets: true-- loads the parent's CSS and JS before yours. Set tofalseonly if you want to completely replace the parent's styles (rare, and you'll need to provide everything yourself).priorityon assets -- controls load order. Parent assets typically have priority 100. Set your child assets to 200+ to ensure they load last and can override parent styles.layouts-- maps page types to layout templates. This is how you get a sidebar on category pages but full-width on product pages.
Le pipeline d''assets : Comment CSS et JS arrivent au navigateur
PrestaShop's asset system has multiple layers, and understanding them prevents the most common "my CSS changes don't show up" problems.
Asset registration sources (in load order):
- theme.yml assets -- registered in the theme configuration file
- Module assets -- registered via
$this->context->controller->registerStylesheet()orregisterJavascript()in the module's hook methods - Controller-specific assets -- registered in the controller's
setMedia()method
The CCC (Combine, Compress, Cache) layer:
When CCC is enabled (Back Office > Advanced Parameters > Performance), PrestaShop:
- Combines all CSS files into one (or a few) combined files
- Minifies the combined CSS
- Names the output with a content hash:
themes/classic/assets/cache/combined-abc123.css - Does the same for JavaScript
This means: after editing a CSS file, you must either increment the cache version (PS_CCCCSS_VERSION in ps_configuration) or delete the combined files in themes/*/assets/cache/. Otherwise the browser continues loading the old combined file because the filename hasn't changed.
This is the #1 cause of "I changed CSS but nothing happened" in PrestaShop. It is not a bug -- it is the caching system working as designed. The fix is always: clear CCC cache, not bypass it.
Moteur de templates Smarty : Ce que les developpeurs de modules doivent savoir
PrestaShop uses Smarty 3/4 for front-office templates. The key concepts:
Variable assignment: Controllers and modules assign data to Smarty, then templates render it.
// In PHP (controller or module hook):
$this->context->smarty->assign('my_data', $someArray);
// In .tpl template:
{$my_data.name}
{$my_data.price|number_format:2}
Block system: Templates define named blocks that child templates can override.
// Parent template (classic/templates/catalog/product.tpl):
{block name='product_description'}
<div class="product-description">{$product.description nofilter}</div>
{/block}
// Child template (my-child/templates/catalog/product.tpl):
{extends file='parent:catalog/product.tpl'}
{block name='product_description'}
<div class="custom-description">
{$product.description_short nofilter}
<hr>
{$product.description nofilter}
</div>
{/block}
The parent: prefix is mandatory in {extends}. Without it, Smarty finds the child's own file and enters an infinite loop that crashes with a timeout. This is the single most common child theme error.
The nofilter modifier: By default, Smarty auto-escapes variables to prevent XSS. For variables that contain HTML (like product descriptions), you must use {$var nofilter}. Without it, HTML tags appear as literal text on the page.
Hook rendering in templates:
{hook h='displayProductAdditionalInfo' product=$product}
{hook h='displayFooter'}
{widget name='ps_emailsubscription' hook='displayFooter'}
The {hook} tag calls Hook::exec(), which runs every module registered on that hook and concatenates their output. The {widget} tag calls a specific module's widget method regardless of hook registration.
Classic vs Hummingbird : Differences architecturales
| Aspect | Classic (PS 1.7/8) | Hummingbird (PS 9) |
|---|---|---|
| CSS framework | Bootstrap 4.6 | Bootstrap 5.3 |
| CSS architecture | Flat specificity | CSS Layers (@layer) |
| JavaScript | jQuery + vanilla | TypeScript + jQuery (compat) |
| Build system | Webpack 4 | Webpack 5 |
| CSS custom properties | Minimal | Extensive (--bs-*) |
| Class naming | Mixed BEM/Bootstrap | BEM-like (.product__name) |
| Dark mode | Not supported | CSS variable-based support |
| Image format | JPG/PNG | WebP default |
The CSS Layers in Hummingbird fundamentally change how specificity works:
@layer vendors, bs-base, bs-components, bs-custom-components,
ps-base, ps-components, ps-pages, ps-modules, utilities;
Styles in later layers override earlier ones regardless of selector specificity. A simple .product__name { color: red; } in the ps-components layer beats a highly specific #content .product-container .product__name { color: blue; } in the bs-components layer. This eliminates !important wars but requires understanding which layer your CSS belongs in.
For module developers targeting both themes: use var(--css-property, fallback-value) pattern. On Classic, CSS custom properties aren't defined, so the fallback wins. On Hummingbird, the custom property is defined and wins. One CSS file serves both themes correctly.
Le sous-repertoire modules/ : Surcharges de templates de modules au niveau du theme
The themes/[theme]/modules/ directory is one of PrestaShop's most powerful and most dangerous features. It lets a theme completely replace how any module renders its templates.
themes/my-child/modules/
ps_featuredproducts/
views/templates/hook/
ps_featuredproducts.tpl <-- replaces the module's own template
ps_searchbar/
views/templates/hook/
ps_searchbar.tpl <-- custom search bar rendering
When to use theme module overrides:
- The module's HTML structure doesn't fit your design
- You need to add/remove elements from a module's output
- You want to change the Smarty logic (show/hide based on conditions)
When NOT to use theme module overrides:
- You want to change CSS only -- use your child theme's CSS instead
- The module provides configuration options for what you want to change -- use those first
- You're overriding a module you update frequently -- every update requires re-checking the override
The maintenance trap: We maintain 140+ modules. When a module updates its template to fix a bug or add a feature, stores with theme overrides of that template never see the fix. The override silently serves the old version. We see this regularly in support -- "the feature doesn't work after updating" turns out to be a stale theme override from 3 versions ago. The fix is always: diff the override against the current module template, merge changes, or delete the override entirely.
Impact sur les performances de l''architecture du theme
Theme choices directly impact page load time:
- Template compilation: First request after cache clear compiles all
.tplfiles to PHP. A theme with 50 template files takes 50-100ms longer on first load. Subsequent requests: 0ms (compiled PHP is cached). - Asset count: Each CSS/JS file is an HTTP request (without CCC). 15 separate CSS files = 15 requests. With CCC enabled: 1-2 requests. Always enable CCC in production.
- Image sizes: The theme's
theme.ymldefines image types (home_default: 250x250,large_default: 800x800). Using oversized image types on listing pages (e.g.,large_defaultfor category thumbnails) wastes bandwidth. Use the smallest type that looks good. - Hook density: Each
{hook}call in a template executes all registered modules. A header with 5 hooks, each with 3 modules, runs 15 module hook methods per page load. Uninstall modules you don't use -- even if they're disabled, some still register hooks. - Smarty
nocache: Content inside{nocache}...{/nocache}blocks is never cached by Smarty and re-evaluates on every request. Use sparingly -- typically only for user-specific data like cart counts or login status.
Problemes courants avec /themes/ et solutions
| Problem | Cause | Fix |
|---|---|---|
| CSS changes don't appear | CCC serving cached combined file | Delete themes/*/assets/cache/*, increment PS_CCCCSS_VERSION |
| Template changes don't appear | Smarty compile cache | rm -rf var/cache/prod/smarty/compile/* |
| Infinite loop / timeout | {extends} without parent: prefix | Use {extends file='parent:catalog/product.tpl'} |
| HTML tags showing as text | Smarty auto-escaping | Use {$var nofilter} for HTML variables |
| Module feature missing after update | Stale theme module override | diff override vs module template, update or delete override |
| Blank page after theme switch | Missing theme.yml or wrong parent | Check config, verify parent theme exists |
| Fonts not loading | Wrong relative path in @font-face | Path must be relative to the CSS file location |
| Mobile layout broken | Missing viewport meta or wrong Bootstrap grid | Check _partials/head.tpl for viewport, verify responsive classes |
Fichiers de ce repertoire que vous devez comprendre
If you're developing modules or customizing themes, these are the files worth reading (in the parent theme):
templates/_partials/head.tpl-- where CSS and meta tags are output. Understand this to debug missing stylesheets.templates/layouts/layout-full-width.tpl-- the page wrapper. Shows you the HTML skeleton: header, content area, footer, hook positions.templates/catalog/product.tpl-- the most complex template. Shows all product page blocks, hook positions, and data variables available.config/theme.yml-- the source of truth for assets, layouts, and theme configuration.assets/css/theme.css-- read (don't edit) to understand the parent's class names and specificity patterns.
For a hands-on guide to creating child themes and customizing templates, see our Child Themes guide in the Knowledge Base.
Next in the Architecture Series: /modules/ Deep Dive -- the complete module lifecycle, from install() to hookDisplay*(), including upgrade scripts, autoloading, and the patterns that 140+ production modules rely on.
Commentaires
Aucun commentaire pour le moment. Soyez le premier !
Laisser un commentaire