Smarty to Twig: why a 1:1 template port is the most expensive way to migrate
Moving Shopware 5 templates to Shopware 6 is not a syntax exercise. Block structure, theme inheritance and the parts you should rebuild instead of translate.
Photo: free stock photography (Unsplash licence) — see imprint
The syntax is the easy 10 percent
Every Smarty-to-Twig conversation starts in the wrong place. Yes, {$sArticle.articleName} becomes {{ page.product.translated.name }}. Yes, {if} becomes {% if %}, {foreach} becomes {% for %}, and the modifier pipe survives almost unchanged. A developer who has never touched Twig is productive in it inside a day. If syntax were the problem, template migration would be a scripted find-and-replace and nobody would need to read this.
The problem is that a Shopware 5 template and a Shopware 6 template are not the same document written in two languages. They describe different page structures, they receive differently shaped data, they sit on different CSS foundations, and they extend differently. Translating the words gets you a Twig file that renders a Shopware 5 page badly inside Shopware 6 — which is the worst of both worlds, because you have paid for a migration and inherited every quirk you were trying to leave behind.
Blocks look the same and behave differently
Both systems have blocks, which is exactly why people get hurt. In Shopware 5 you write {extends file='parent:frontend/detail/index.tpl'} and then {block name='frontend_detail_index_buy'}{$smarty.block.parent}...{/block}. In Shopware 6 you write {% sw_extends '@Storefront/storefront/page/product-detail/index.html.twig' %} and {% block page_product_detail_buy %}{{ parent() }}...{% endblock %}. The shapes rhyme. The block names do not, the file paths do not, and the granularity does not.
The one that costs real money is sw_extends versus plain Twig extends. Shopware's own directive is what makes multi-level inheritance work — your theme extending a plugin extending another plugin extending the Storefront. Use vanilla {% extends %} in a theme and you break that chain silently: your override wins, everything below it disappears, and nobody notices until a plugin update stops appearing on the page three months later. This is the single most common template bug we find in migrated shops.
A 1:1 port is a trap because the data is not there any more
Shopware 5 templates were fed arrays assembled by the controller — $sArticle, $sBasket, $sCategoryContent — flat structures full of legacy field names that had accreted over a decade. Shopware 6 hands the template a page object built by a page loader, with entities, associations, translated fields and struct extensions. There is no mechanical mapping. Half the values your old template reads do not exist under any name; the other half exist somewhere you have to go and find, and sometimes you have to add a page-loader subscriber to put them there at all.
Underneath that, the CSS story changed too. Shopware 5 themes sat on Bootstrap 3 and LESS; Shopware 6 Storefront sits on Bootstrap and SCSS with its own variables and a different grid vocabulary. Your old markup is full of classes that either no longer exist or now mean something else. Porting the HTML class-for-class produces a page that looks approximately right on a designer's monitor and falls apart at 768 pixels, and you spend the last third of the project fixing responsive bugs you imported on purpose.
Rebuild these, translate those
The useful discipline is to sort every template into three buckets before anyone opens an editor. Delete: everything that exists because Shopware 5 could not do it and Shopware 6 does natively. Rebuild: everything where the old markup encodes an old page structure. Translate: the small remainder that is genuinely just your content in someone else's syntax. In every migration we have done, the delete bucket is bigger than the client expects and the translate bucket is far smaller.
The delete bucket is where the money is. That custom landing page template with the hand-built teaser grid is a Shopping Experience layout now, and a marketing person can maintain it without you. Those emotion-world overrides are gone by definition. The little template hacks that inject a badge or reorder a block are often a CMS block or a two-line override in Shopware 6. Every file you delete is a file nobody has to test, nobody has to fix on the next minor, and nobody has to explain to the developer who inherits this in 2027.
- Always sw_extends, never plain extends, in themes and plugins — silent inheritance breakage otherwise.
- Override the smallest block that does the job; never copy a whole template to change one line.
- Emotion worlds and custom landing templates become Shopping Experiences — delete, do not port.
- Data the page object does not carry needs a page-loader subscriber, not a database call in the template.
- Rebuild markup against the current Storefront classes instead of importing your Bootstrap 3 grid.
What this actually costs, and how to keep the bill down
Template work is usually the largest single line item in a Shopware 5 to 6 migration, and it is the one clients underestimate hardest, because the old shop looks finished. A heavily customised SW5 theme with a few hundred template overrides is months of work — not because Twig is hard, but because somebody has to open each of those overrides, work out what it was for, find out whether that reason still exists, and decide. That decision, repeated three hundred times, is the project.
So the cheapest lever is not a faster developer, it is a shorter list. Go through your SW5 overrides with the person who asked for them and kill the ones that exist because of a campaign in 2019, a manager who has left, or a fashion that has passed. Then rebuild on a near-standard Shopware 6 theme and add customisation back only where somebody can name the revenue it protects. Shops that do this land near the bottom of the estimate. Shops that insist on pixel-identity with a design from 2017 land near the top, and then ask why.
| Concept | Shopware 5 / Smarty | Shopware 6 / Twig |
|---|---|---|
| Extend a parent template | {extends file='parent:...'} | {% sw_extends '@Storefront/...' %} — never plain extends |
| Keep the parent block content | {$smarty.block.parent} | {{ parent() }} |
| Product data in the template | Flat controller array, $sArticle.* | Page object with entities and translated fields |
| Styling foundation | Bootstrap 3, LESS | Current Bootstrap, SCSS, theme variables |
| Editorial landing pages | Emotion worlds plus custom templates | Shopping Experiences — usually delete the template |
- Syntax conversion is a day of learning; deciding what each of your 300 overrides was for is the actual project.
- Plain {% extends %} in a theme silently kills plugin inheritance — sw_extends or nothing.
- The biggest saving in a template migration is the files you delete, not the ones you convert faster.
- Insisting on pixel-identity with your old theme is the most expensive decision available to you.
Frequently asked questions
The syntax can be, roughly. The result is worthless. An automated converter cannot know that the variable your template reads no longer exists in the page object, that the block you hook into has a different name and granularity, or that the whole template should be deleted because Shopping Experiences replaced it. You would spend longer fixing the machine output than writing the file properly.
It scales with the number of overrides, not with the number of pages. A shop close to the standard theme with a handful of overrides is a few weeks. A heavily customised theme with several hundred overrides is months, because each one needs a human decision about whether its reason still exists. Count your override files before you ask anyone for a number — that count is the estimate.
Plain Twig extends resolves to one parent. sw_extends is Shopware's directive that preserves the full inheritance chain, so your theme, your plugins and the Storefront can all contribute to the same template. Using plain extends in a theme means every plugin extension below you is dropped without an error message. The page still renders, it just quietly loses features.
Rebuild against the standard theme unless your design demonstrably earns money that a standard one would not. Porting a 2017 design means importing its Bootstrap 3 markup, its responsive behaviour and its assumptions into a system built on none of them — you pay full price for a theme that starts life as legacy. Most shops discover their design was never the reason customers bought.
We do this for a living — Shopware, Node.js, React, ERP integration and automation for B2B.
Talk to an engineer