Using menus
A menu is an editable navigation record. Its type defines the settings and rendering template; its items hold translated labels, route selections, and nested children.
Follow Configure menus to create a main type and Create a menu to populate its record.
Render shared navigation
{{ sfs_cms_menu('main') }}
The renderer loads the menu record by type and passes it to @menu/main/render.html.twig as menu. Creating the type alone does not create that record. A menu link does not create or publish its destination page.
Render nested items
This template handles route items and submenu headings:
{# cms/menus/main/render.html.twig #}
{% macro items(entries) %}
<ul>
{% for item in entries %}
<li>
{% if item.symfonyRoute.route_name|default(false) %}
<a href="{{ sfs_cms_url(item.symfonyRoute) }}">
{{ item.text|sfs_cms_trans }}
</a>
{% else %}
<span>{{ item.text|sfs_cms_trans }}</span>
{% endif %}
{% if item.items|length %}
{{ _self.items(item.items) }}
{% endif %}
</li>
{% endfor %}
</ul>
{% endmacro %}
<nav aria-label="Main navigation">
{{ _self.items(menu.items|filter(item => item.parent is null)) }}
</nav>
The translation filter resolves labels for the active locale. The CMS URL helper also handles CMS route references. Keep normal Twig escaping for labels.
Add menu settings
Use form_fields in the type for settings such as a translated navigation heading. The editable item list remains available when items: true. The template can read additional saved settings from the menu's data.
Cache the correct context
Menus can use ESI and cache_ttl; use direct rendering while validating a new template. Check the result for every site and language. Avoid caching personalized navigation as public shared HTML. Permission-dependent application navigation may be better rendered by the application outside the shared CMS menu.