8.4 Shop: Template Migration
This step converts shop templates from Semantic UI to Bootstrap 5 and integrates them with Sylius 2.0 Twig Hooks system.
When to skip this step:
Your plugin has no shop templates
Templates are already using Bootstrap 5 and Twig Hooks
When to do this step:
You have shop pages using Semantic UI
You want to make shop templates customizable in end application
📊 Overview & Approach
Shop templates are simpler than admin forms:
Usually custom pages (product list, resource details, etc.)
No forms or Live Components (typically)
Focus on content presentation
Two Approaches:
A. Extend Existing Sylius Hooks (for adding content to existing pages)
Add your content to checkout, cart, product pages, etc.
Use existing Sylius hook structure
Example: Add custom checkbox to checkout
B. Custom Hooks for Custom Pages (RECOMMENDED for new pages)
Create your own hook hierarchy
Full control over structure
End application can customize everything
Example: Resource show page, custom product listing
✅ RECOMMENDED: Custom Hooks for Custom Pages
Use this when creating new pages in your plugin (like resource detail page, custom listings, etc.).
Hook Naming Convention
Critical principle: Each template file runs ONE hook, and that hook is named after the file.
Pattern:
show.html.twig
runs hookplugin.shop.{page}.show
content.html.twig
runs hookcontent
(relative) orplugin.shop.{page}.{action}.content
(absolute)Hook name = file location pattern
Relative vs Absolute Hook Names:
Relative:
{% hook 'content' %}
- automatically resolves based on parent hook contextAbsolute:
{% hook 'plugin.shop.terms.show.content' %}
- explicit full pathRecommended: Use relative names for cleaner, more maintainable code
Example hierarchy:
show.html.twig
→ runs hook: 'plugin.shop.terms.show' (absolute)
→ renders hookable: 'content' (loads content.html.twig)
content.html.twig
→ runs hook: 'content' (relative, resolves to 'plugin.shop.terms.show.content')
→ renders hookables: 'breadcrumbs', 'header', 'main' (loads individual templates)
YAML configuration defines hookables (child templates), not the hooks called in templates.
1. Create main page template
templates/shop/resource/show.html.twig
:
{% extends '@SyliusShop/shared/layout/base.html.twig' %}
{% block title %}{{ resource.name }} | {{ parent() }}{% endblock %}
{% block content %}
{% hook 'plugin.shop.resource.show' with { resource: resource } %}
{% endblock %}
Key points:
Extend Sylius base layout
Use
{% hook %}
tag to make content customizablePass variables needed by sub-templates
2. Create hook configuration
mkdir -p config/twig_hooks/shop/resource
config/twig_hooks/shop/resource/show.yaml
:
sylius_twig_hooks:
hooks:
'plugin.shop.resource.show':
content:
template: '@Plugin/shop/resource/show/content.html.twig'
priority: 0
'plugin.shop.resource.show.content':
header:
template: '@Plugin/shop/resource/show/content/header.html.twig'
priority: 100
main:
template: '@Plugin/shop/resource/show/content/main.html.twig'
priority: 0
Hook naming convention:
plugin.shop.{page}.{action}
- top levelplugin.shop.{page}.{action}.{section}
- sectionsUse priorities to control order (higher = first)
3. Create section templates
templates/shop/resource/show/content.html.twig
:
<div class="container my-5">
{% hook 'content' %}
</div>
Note on hook names:
Use relative hook names:
{% hook 'content' %}
automatically resolves toplugin.shop.resource.show.content
in this contextAlternatively use absolute names:
{% hook 'plugin.shop.resource.show.content' %}
The hookables (
header
,main
, etc.) are defined in YAML and rendered automatically by this hook
templates/shop/resource/show/content/header.html.twig
:
{% set resource = hookable_metadata.context.resource %}
<div class="row mb-4">
<div class="col-12">
<h1>{{ resource.name }}</h1>
</div>
</div>
templates/shop/resource/show/content/main.html.twig
:
{% set resource = hookable_metadata.context.resource %}
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-body">
{{ resource.content|raw }}
</div>
</div>
</div>
</div>
Key points:
Access variables via
hookable_metadata.context.{variable}
Use Bootstrap 5 classes:
container
,row
,col-*
,card
Each section in separate template = customizable
templates/shop/resource/show/content/breadcrumbs.html.twig
(optional but recommended):
{% set resource = hookable_metadata.context.resource %}
{% from '@SyliusShop/shared/breadcrumbs.html.twig' import breadcrumbs as breadcrumbs %}
{{ breadcrumbs([
{ label: 'sylius.ui.home'|trans, path: path('sylius_shop_homepage')},
{ label: resource.name, active: true }
]) }}
Important: Use Sylius breadcrumbs macro (@SyliusShop/shared/breadcrumbs.html.twig
) instead of writing HTML manually. This ensures consistent styling with Sylius shop and makes breadcrumbs automatically responsive.
4. Import Twig Hooks config
# config/config.yaml
imports:
- { resource: "twig_hooks/**/*.yaml" }
5. Validate
vendor/bin/console cache:clear
vendor/bin/console debug:twig-hooks | grep -i "shop"
📝 Alternative: Extend Existing Sylius Hooks
Use this to add content to existing Sylius pages (checkout, cart, product, etc.).
1. Find existing Sylius hooks
Option A: Symfony Profiler (recommended)
Open the page in browser
Open Symfony Profiler → Twig Hooks tab
See all available hooks
Option B: Grep vendor files
grep -r "sylius_twig_hooks:" vendor/sylius/sylius/src/Sylius/Bundle/ShopBundle/config/twig_hooks/
2. Create hook configuration
config/twig_hooks/shop/checkout/complete.yaml
:
sylius_twig_hooks:
hooks:
'sylius_shop.checkout.complete.content.form':
custom_checkbox:
template: '@Plugin/shop/checkout/complete/content/form/custom.html.twig'
priority: 50
3. Create template
templates/shop/checkout/complete/content/form/custom.html.twig
:
{% set form = hookable_metadata.context.form %}
<div class="mb-3">
{{ form_row(form.custom_field) }}
</div>
When to use this:
Adding fields to existing forms
Injecting content into existing pages
Extending without replacing
When NOT to use this:
Creating new pages → Use custom hooks
Need full control → Use custom hooks
🎨 Bootstrap 5 Migration
Sylius 2.0 uses Bootstrap 5 instead of Semantic UI. Use standard Bootstrap classes in your templates.
Common patterns:
Container:
container
,container-fluid
Grid:
row
,col-*
Cards:
card
,card-header
,card-body
Buttons:
btn btn-primary
🔍 Finding Existing Sylius Hooks
For developers (manual):
Open shop page in browser
Open Symfony Profiler → Twig Hooks tab
See all available hooks with their structure
Copy hook name and use in your config
For AI:
grep -r "sylius_twig_hooks:" vendor/sylius/sylius/src/Sylius/Bundle/ShopBundle/config/twig_hooks/ | grep -i "{keyword}"
✅ Summary
Recommended approach for shop:
Custom pages: Use custom hooks (full control)
Extending existing pages: Extend Sylius hooks (minimal changes)
Bootstrap 5: Use modern classes
No Live Components needed: Shop is usually static content
Key principles:
Custom hooks for new pages = maximum flexibility
Extend Sylius hooks for small additions
Bootstrap 5:
container
,row
,col-*
,card
classesPass all needed variables through hook context
Each section = separate template = customizable
What end applications can do:
Disable any section
Reorder sections
Override templates
Add new sections
Change priorities
Last updated
Was this helpful?