.. index:: single: Templating
A template is the best way to organize and render HTML from inside your application, whether you need to render HTML from a :doc:`controller </controller>` or generate the :doc:`contents of an email </mailer>`. Templates in Symfony are created with Twig: a flexible, fast, and secure template engine.
The Twig templating language allows you to write concise, readable templates that are more friendly to web designers and, in several ways, more powerful than PHP templates. Take a look at the following Twig template example. Even if it's the first time you see Twig, you probably understand most of it:
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Symfony!</title>
</head>
<body>
<h1>{{ page_title }}</h1>
{% if user.isLoggedIn %}
Hello {{ user.name }}!
{% endif %}
{# ... #}
</body>
</html>
Twig syntax is based on these three constructs:
{{ ... }}
, used to display the content of a variable or the result of evaluating an expression;{% ... %}
, used to run some logic, such as a conditional or a loop;{# ... #}
, used to add comments to the template (unlike HTML comments, these comments are not included in the rendered page).
You can't run PHP code inside Twig templates, but Twig provides utilities to
run some logic in the templates. For example, filters modify content before
being rendered, like the upper
filter to uppercase contents:
{{ title|upper }}
Twig comes with a long list of tags, filters and functions that are available by default. In Symfony applications you can also use these :doc:`Twig filters and functions defined by Symfony </reference/twig_reference>` and you can :doc:`create your own Twig filters and functions </templating/twig_extension>`.
Twig is fast in the prod
:ref:`environment <configuration-environments>`
(because templates are compiled into PHP and cached automatically), but
convenient to use in the dev
environment (because templates are recompiled
automatically when you change them).
Twig has several configuration options to define things like the format used to display numbers and dates, the template caching, etc. Read the :doc:`Twig configuration reference </reference/configuration/twig>` to learn about them.
Before explaining in detail how to create and render templates, look at the
following example for a quick overview of the whole process. First, you need to
create a new file in the templates/
directory to store the template contents:
{# templates/user/notifications.html.twig #}
<h1>Hello {{ user_first_name }}!</h1>
<p>You have {{ notifications|length }} new notifications.</p>
Then, create a :doc:`controller </controller>` that renders this template and passes to it the needed variables:
// src/Controller/UserController.php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; class UserController extends AbstractController { // ... public function notifications() { // get the user information and notifications somehow $userFirstName = '...'; $userNotifications = ['...', '...']; // the template path is the relative file path from `templates/` return $this->render('user/notifications.html.twig', [ // this array defines the variables passed to the template, // where the key is the variable name and the value is the variable value // (Twig recommends using snake_case variable names: 'foo_bar' instead of 'fooBar') 'user_first_name' => $userFirstName, 'notifications' => $userNotifications, ]); } }
Symfony recommends the following for template names:
- Use snake case for filenames and directories (e.g.
blog_posts.twig
,admin/default_theme/blog/index.twig
, etc.); - Define two extensions for filenames (e.g.
index.html.twig
orblog_posts.xml.twig
) being the first extension (html
,xml
, etc.) the final format that the template will generate.
Although templates usually generate HTML contents, they can generate any text-based format. That's why the two-extension convention simplifies the way templates are created and rendered for multiple formats.
Templates are stored by default in the templates/
directory. When a service
or controller renders the product/index.html.twig
template, they are actually
referring to the <your-project>/templates/product/index.html.twig
file.
The default templates directory is configurable with the :ref:`twig.default_path <config-twig-default-path>` option and you can add more template directories :ref:`as explained later <templates-namespaces>` in this article.
A common need for templates is to print the values stored in the templates passed from the controller or service. Variables usually store objects and arrays instead of strings, numbers and boolean values. That's why Twig provides quick access to complex PHP variables. Consider the following template:
<p>{{ user.name }} added this comment on {{ comment.publishedAt|date }}</p>
The user.name
notation means that you want to display some information
(name
) stored in a variable (user
). Is user
an array or an object?
Is name
a property or a method? In Twig this doesn't matter.
When using the foo.bar
notation, Twig tries to get the value of the variable
in the following order:
$foo['bar']
(array and element);$foo->bar
(object and public property);$foo->bar()
(object and public method);$foo->getBar()
(object and getter method);$foo->isBar()
(object and isser method);$foo->hasBar()
(object and hasser method);- If none of the above exists, use
null
.
This allows to evolve your application code without having to change the template code (you can start with array variables for the application proof of concept, then move to objects with methods, etc.)
Instead of writing the link URLs by hand, use the path()
function to
generate URLs based on the :ref:`routing configuration <routing-creating-routes>`.
Later, if you want to modify the URL of a particular page, all you'll need to do is change the routing configuration: the templates will automatically generate the new URL.
Consider the following routing configuration:
.. configuration-block:: .. code-block:: php-annotations // src/Controller/BlogController.php namespace App\Controller; // ... use Symfony\Component\Routing\Annotation\Route; class BlogController extends AbstractController { /** * @Route("/", name="blog_index") */ public function index() { // ... } /** * @Route("/article/{slug}", name="blog_post") */ public function show(string $slug) { // ... } } .. code-block:: yaml # config/routes.yaml blog_index: path: / controller: App\Controller\BlogController::index blog_post: path: /article/{slug} controller: App\Controller\BlogController::show .. code-block:: xml <!-- config/routes.xml --> <?xml version="1.0" encoding="UTF-8" ?> <routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing https://symfony.com/schema/routing/routing-1.0.xsd"> <route id="blog_index" path="/" controller="App\Controller\BlogController::index"/> <route id="blog_post" path="/article/{slug}" controller="App\Controller\BlogController::show"/> </routes> .. code-block:: php // config/routes.php use App\Controller\BlogController; use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; return function (RoutingConfigurator $routes) { $routes->add('blog_index', '/') ->controller([BlogController::class, 'index']) ; $routes->add('blog_post', '/articles/{slug}') ->controller([BlogController::class, 'show']) ; };
Use the path()
Twig function to link to these pages and pass the route name
as the first argument and the route parameters as the optional second argument:
<a href="{{ path('blog_index') }}">Homepage</a>
{# ... #}
{% for post in blog_posts %}
<h1>
<a href="{{ path('blog_post', {slug: post.slug}) }}">{{ post.title }}</a>
</h1>
<p>{{ post.excerpt }}</p>
{% endfor %}
The path()
function generates relative URLs. If you need to generate
absolute URLs (for example when rendering templates for emails or RSS feeds),
use the url()
function, which takes the same arguments as path()
(e.g. <a href="{{ url('blog_index') }}"> ... </a>
).
If a template needs to link to a static asset (e.g. an image), Symfony provides
an asset()
Twig function to help generate that URL. First, install the
asset
package:
$ composer require symfony/asset
You can now use the asset()
function:
{# the image lives at "public/images/logo.png" #}
<img src="{{ asset('images/logo.png') }}" alt="Symfony!"/>
{# the CSS file lives at "public/css/blog.css" #}
<link href="{{ asset('css/blog.css') }}" rel="stylesheet"/>
{# the JS file lives at "public/bundles/acme/js/loader.js" #}
<script src="{{ asset('bundles/acme/js/loader.js') }}"></script>
The asset()
function's main purpose is to make your application more portable.
If your application lives at the root of your host (e.g. https://example.com
),
then the rendered path should be /images/logo.png
. But if your application
lives in a subdirectory (e.g. https://example.com/my_app
), each asset path
should render with the subdirectory (e.g. /my_app/images/logo.png
). The
asset()
function takes care of this by determining how your application is
being used and generating the correct paths accordingly.
Tip
The asset()
function supports various cache busting techniques via the
:ref:`version <reference-framework-assets-version>`,
:ref:`version_format <reference-assets-version-format>`, and
:ref:`json_manifest_path <reference-assets-json-manifest-path>` configuration options.
Tip
If you'd like help packaging, versioning and minifying your JavaScript and CSS assets in a modern way, read about :doc:`Symfony's Webpack Encore </frontend>`.
If you need absolute URLs for assets, use the absolute_url()
Twig function
as follows:
<img src="{{ absolute_url(asset('images/logo.png')) }}" alt="Symfony!"/>
<link rel="shortcut icon" href="{{ absolute_url('favicon.png') }}">
Symfony creates a context object that is injected into every Twig template
automatically as a variable called app
. It provides access to some
application information:
<p>Username: {{ app.user.username ?? 'Anonymous user' }}</p>
{% if app.debug %}
<p>Request method: {{ app.request.method }}</p>
<p>Application Environment: {{ app.environment }}</p>
{% endif %}
The app
variable (which is an instance of :class:`Symfony\\Bridge\\Twig\\AppVariable`)
gives you access to these variables:
app.user
- The :ref:`current user object <create-user-class>` or
null
if the user is not authenticated. app.request
- The :class:`Symfony\\Component\\HttpFoundation\\Request` object that stores the current :ref:`request data <accessing-request-data>` (depending on your application, this can be a :ref:`sub-request <http-kernel-sub-requests>` or a regular request).
app.session
- The :class:`Symfony\\Component\\HttpFoundation\\Session\\Session` object that
represents the current :doc:`user's session </session>` or
null
if there is none. app.flashes
- An array of all the :ref:`flash messages <flash-messages>` stored in the session.
You can also get only the messages of some type (e.g.
app.flashes('notice')
). app.environment
- The name of the current :ref:`configuration environment <configuration-environments>`
(
dev
,prod
, etc). app.debug
- True if in :ref:`debug mode <debug-mode>`. False otherwise.
app.token
- A :class:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface` object representing the security token.
In addition to the global app
variable injected by Symfony, you can also
:doc:`inject variables automatically to all Twig templates </templating/global_variables>`.
If your controller extends from the :ref:`AbstractController <the-base-controller-class-services>`,
use the render()
helper:
// src/Controller/ProductController.php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; class ProductController extends AbstractController { public function index() { // ... // the `render()` method returns a `Response` object with the // contents created by the template return $this->render('product/index.html.twig', [ 'category' => '...', 'promotions' => ['...', '...'], ]); // the `renderView()` method only returns the contents created by the // template, so you can use those contents later in a `Response` object $contents = $this->renderView('product/index.html.twig', [ 'category' => '...', 'promotions' => ['...', '...'], ]); return new Response($contents); } }
If your controller does not extend from AbstractController
, you'll need to
:ref:`fetch services in your controller <controller-accessing-services>` and
use the render()
method of the twig
service.
Inject the twig
Symfony service into your own services and use its
render()
method. When using :doc:`service autowiring </service_container/autowiring>`
you only need to add an argument in the service constructor and type-hint it with
the :class:`Twig\\Environment` class:
// src/Service/SomeService.php namespace App\Service; use Twig\Environment; class SomeService { private $twig; public function __construct(Environment $twig) { $this->twig = $twig; } public function someMethod() { // ... $htmlContents = $this->twig->render('product/index.html.twig', [ 'category' => '...', 'promotions' => ['...', '...'], ]); } }
Read the docs about the :ref:`mailer and Twig integration <mailer-twig>`.
Although templates are usually rendered in controllers and services, you can render static pages that don't need any variables directly from the route definition. Use the special :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\TemplateController` provided by Symfony:
.. configuration-block:: .. code-block:: yaml # config/routes.yaml acme_privacy: path: /privacy controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController defaults: # the path of the template to render template: 'static/privacy.html.twig' # special options defined by Symfony to set the page cache maxAge: 86400 sharedAge: 86400 # whether or not caching should apply for client caches only private: true # optionally you can define some arguments passed to the template context: site_name: 'ACME' theme: 'dark' .. code-block:: xml <!-- config/routes.xml --> <?xml version="1.0" encoding="UTF-8" ?> <routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing https://symfony.com/schema/routing/routing-1.0.xsd"> <route id="acme_privacy" path="/privacy" controller="Symfony\Bundle\FrameworkBundle\Controller\TemplateController"> <!-- the path of the template to render --> <default key="template">static/privacy.html.twig</default> <!-- special options defined by Symfony to set the page cache --> <default key="maxAge">86400</default> <default key="sharedAge">86400</default> <!-- whether or not caching should apply for client caches only --> <default key="private">true</default> <!-- optionally you can define some arguments passed to the template --> <default key="context"> <default key="site_name">ACME</default> <default key="theme">dark</default> </default> </route> </routes> .. code-block:: php // config/routes.php use Symfony\Bundle\FrameworkBundle\Controller\TemplateController; use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; return function (RoutingConfigurator $routes) { $routes->add('acme_privacy', '/privacy') ->controller(TemplateController::class) ->defaults([ // the path of the template to render 'template' => 'static/privacy.html.twig', // special options defined by Symfony to set the page cache 'maxAge' => 86400, 'sharedAge' => 86400, // whether or not caching should apply for client caches only 'private' => true, // optionally you can define some arguments passed to the template 'context' => [ 'site_name' => 'ACME', 'theme' => 'dark', ] ]) ; };
.. versionadded:: 5.1 The ``context`` option was introduced in Symfony 5.1.
Templates are loaded in the application using a Twig template loader, which also provides a method to check for template existence. First, get the loader:
// in a controller extending from AbstractController $loader = $this->get('twig')->getLoader(); // in a service using autowiring use Twig\Environment; public function __construct(Environment $twig) { $loader = $twig->getLoader(); }
Then, pass the path of the Twig template to the exists()
method of the loader:
if ($loader->exists('theme/layout_responsive.html.twig')) { // the template exists, do something // ... }
Symfony provides several utilities to help you debug issues in your templates.
The lint:twig
command checks that your Twig templates don't have any syntax
errors. It's useful to run it before deploying your application to production
(e.g. in your continuous integration server):
# check all the application templates
$ php bin/console lint:twig
# you can also check directories and individual templates
$ php bin/console lint:twig templates/email/
$ php bin/console lint:twig templates/article/recent_list.html.twig
# you can also show the deprecated features used in your templates
$ php bin/console lint:twig --show-deprecations templates/email/
The debug:twig
command lists all the information available about Twig
(functions, filters, global variables, etc.). It's useful to check if your
:doc:`custom Twig extensions </templating/twig_extension>` are working properly
and also to check the Twig features added when :ref:`installing packages <symfony-flex>`:
# list general information
$ php bin/console debug:twig
# filter output by any keyword
$ php bin/console debug:twig --filter=date
# pass a template path to show the physical file which will be loaded
$ php bin/console debug:twig @Twig/Exception/error.html.twig
Symfony provides a :ref:`dump() function <components-var-dumper-dump>` as an
improved alternative to PHP's var_dump()
function. This function is useful
to inspect the contents of any variable and you can use it in Twig templates too.
First, make sure that the VarDumper component is installed in the application:
$ composer require symfony/var-dumper
Then, use either the {% dump %}
tag or the {{ dump() }}
function
depending on your needs:
{# templates/article/recent_list.html.twig #}
{# the contents of this variable are sent to the Web Debug Toolbar
instead of dumping them inside the page contents #}
{% dump articles %}
{% for article in articles %}
{# the contents of this variable are dumped inside the page contents
and they are visible on the web page #}
{{ dump(article) }}
<a href="/article/{{ article.slug }}">
{{ article.title }}
</a>
{% endfor %}
To avoid leaking sensitive information, the dump()
function/tag is only
available in the dev
and test
:ref:`configuration environments <configuration-environments>`.
If you try to use it in the prod
environment, you will see a PHP error.
If certain Twig code is repeated in several templates, you can extract it into a single "template fragment" and include it in other templates. Imagine that the following code to display the user information is repeated in several places:
{# templates/blog/index.html.twig #}
{# ... #}
<div class="user-profile">
<img src="{{ user.profileImageUrl }}" alt="{{ user.fullName }}"/>
<p>{{ user.fullName }} - {{ user.email }}</p>
</div>
First, create a new Twig template called blog/_user_profile.html.twig
(the
_
prefix is optional, but it's a convention used to better differentiate
between full templates and template fragments).
Then, remove that content from the original blog/index.html.twig
template
and add the following to include the template fragment:
{# templates/blog/index.html.twig #}
{# ... #}
{{ include('blog/_user_profile.html.twig') }}
The include()
Twig function takes as argument the path of the template to
include. The included template has access to all the variables of the template
that includes it (use the with_context option to control this).
You can also pass variables to the included template. This is useful for example
to rename variables. Imagine that your template stores the user information in a
variable called blog_post.author
instead of the user
variable that the
template fragment expects. Use the following to rename the variable:
{# templates/blog/index.html.twig #}
{# ... #}
{{ include('blog/_user_profile.html.twig', {user: blog_post.author}) }}
:ref:`Including template fragments <templates-include>` is useful to reuse the same content on several pages. However, this technique is not the best solution in some cases.
Imagine that the template fragment displays the three most recent blog articles.
To do that, it needs to make a database query to get those articles. When using
the include()
function, you'd need to do the same database query in every
page that includes the fragment. This is not very convenient.
A better alternative is to embed the result of executing some controller
with the render()
and controller()
Twig functions.
First, create the controller that renders a certain number of recent articles:
// src/Controller/BlogController.php namespace App\Controller; // ... class BlogController extends AbstractController { public function recentArticles($max = 3) { // get the recent articles somehow (e.g. making a database query) $articles = ['...', '...', '...']; return $this->render('blog/_recent_articles.html.twig', [ 'articles' => $articles ]); } }
Then, create the blog/_recent_articles.html.twig
template fragment (the
_
prefix in the template name is optional, but it's a convention used to
better differentiate between full templates and template fragments):
{# templates/blog/_recent_articles.html.twig #}
{% for article in articles %}
<a href="{{ path('blog_show', {slug: article.slug}) }}">
{{ article.title }}
</a>
{% endfor %}
Now you can call to this controller from any template to embed its result:
{# templates/base.html.twig #}
{# ... #}
<div id="sidebar">
{# if the controller is associated with a route, use the path() or url() functions #}
{{ render(path('latest_articles', {max: 3})) }}
{{ render(url('latest_articles', {max: 3})) }}
{# if you don't want to expose the controller with a public URL,
use the controller() function to define the controller to execute #}
{{ render(controller(
'App\\Controller\\BlogController::recentArticles', {max: 3}
)) }}
</div>
When using the controller()
function, controllers are not accessed using a
regular Symfony route but through a special URL used exclusively to serve those
template fragments. Configure that special URL in the fragments
option:
.. configuration-block:: .. code-block:: yaml # config/packages/framework.yaml framework: # ... fragments: { path: /_fragment } .. code-block:: xml <!-- config/packages/framework.xml --> <?xml version="1.0" encoding="UTF-8" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:framework="http://symfony.com/schema/dic/symfony" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> <!-- ... --> <framework:config> <framework:fragment path="/_fragment"/> </framework:config> </container> .. code-block:: php // config/packages/framework.php $container->loadFromExtension('framework', [ // ... 'fragments' => ['path' => '/_fragment'], ]);
Caution!
Embedding controllers requires making requests to those controllers and rendering some templates as result. This can have a significant impact on the application performance if you embed lots of controllers. If possible, :doc:`cache the template fragment </http_cache/esi>`.
.. seealso:: Templates can also :doc:`embed contents asynchronously </templating/hinclude>` with the ``hinclude.js`` JavaScript library.
As your application grows you'll find more and more repeated elements between pages, such as headers, footers, sidebars, etc. :ref:`Including templates <templates-include>` and :ref:`embedding controllers <templates-embed-controllers>` can help, but when pages share a common structure, it's better to use inheritance.
The concept of Twig template inheritance is similar to PHP class inheritance. You define a parent template that other templates can extend from and child templates can override parts of the parent template.
Symfony recommends the following three-level template inheritance for medium and complex applications:
templates/base.html.twig
, defines the common elements of all application templates, such as<head>
,<header>
,<footer>
, etc.;templates/layout.html.twig
, extends frombase.html.twig
and defines the content structure used in all or most of the pages, such as a two-column content + sidebar layout. Some sections of the application can define their own layouts (e.g.templates/blog/layout.html.twig
);templates/*.html.twig
, the application pages which extend from the mainlayout.html.twig
template or any other section layout.
In practice, the base.html.twig
template would look like this:
{# templates/base.html.twig #}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% block title %}My Application{% endblock %}</title>
{% block stylesheets %}
<link rel="stylesheet" type="text/css" href="/css/base.css"/>
{% endblock %}
</head>
<body>
{% block body %}
<div id="sidebar">
{% block sidebar %}
<ul>
<li><a href="{{ path('homepage') }}">Home</a></li>
<li><a href="{{ path('blog_index') }}">Blog</a></li>
</ul>
{% endblock %}
</div>
<div id="content">
{% block content %}{% endblock %}
</div>
{% endblock %}
</body>
</html>
The Twig block tag defines the page sections that can be overridden in the
child templates. They can be empty, like the content
block or define a default
content, like the title
block, which is displayed when child templates don't
override them.
The blog/layout.html.twig
template could be like this:
{# templates/blog/layout.html.twig #}
{% extends 'base.html.twig' %}
{% block content %}
<h1>Blog</h1>
{% block page_contents %}{% endblock %}
{% endblock %}
The template extends from base.html.twig
and only defines the contents of
the content
block. The rest of the parent template blocks will display their
default contents. However, they can be overridden by the third-level inheritance
template, such as blog/index.html.twig
, which displays the blog index:
{# templates/blog/index.html.twig #}
{% extends 'blog/layout.html.twig' %}
{% block title %}Blog Index{% endblock %}
{% block page_contents %}
{% for article in articles %}
<h2>{{ article.title }}</h2>
<p>{{ article.body }}</p>
{% endfor %}
{% endblock %}
This template extends from the second-level template (blog/layout.html.twig
)
but overrides blocks of different parent templates: page_contents
from
blog/layout.html.twig
and title
from base.html.twig
.
When you render the blog/index.html.twig
template, Symfony uses three
different templates to create the final contents. This inheritance mechanism
boosts your productivity because each template includes only its unique contents
and leaves the repeated contents and HTML structure to some parent templates.
Caution!
When using extends
, a child template is forbidden to define template
parts outside of a block. The following code throws a SyntaxError
:
{# app/Resources/views/blog/index.html.twig #}
{% extends 'base.html.twig' %}
{# the line below is not captured by a "block" tag #}
<div class="alert">Some Alert</div>
{# the following is valid #}
{% block content %}My cool blog posts{% endblock %}
Read the Twig template inheritance docs to learn more about how to reuse parent block contents when overriding templates and other advanced features.
Imagine that your template includes the Hello {{ name }}
code to display the
user name. If a malicious user sets <script>alert('hello!')</script>
as
their name and you output that value unchanged, the application will display a
JavaScript popup window.
This is known as a Cross-Site Scripting (XSS) attack. And while the previous example seems harmless, the attacker could write more advanced JavaScript code to perform malicious actions.
To prevent this attack, use "output escaping" to transform the characters
which have special meaning (e.g. replace <
by the <
HTML entity).
Symfony applications are safe by default because they perform automatic output
escaping thanks to the :ref:`Twig autoescape option <config-twig-autoescape>`:
<p>Hello {{ name }}</p>
{# if 'name' is '<script>alert('hello!')</script>', Twig will output this:
'<p>Hello <script>alert('hello!')</script></p>' #}
If you are rendering a variable that is trusted and contains HTML contents, use the Twig raw filter to disable the output escaping for that variable:
<h1>{{ product.title|raw }}</h1>
{# if 'product.title' is 'Lorem <strong>Ipsum</strong>', Twig will output
exactly that instead of 'Lorem <strong>Ipsum</strong>' #}
Read the Twig output escaping docs to learn more about how to disable output escaping for a block or even an entire template.
Although most applications store their templates in the default templates/
directory, you may need to store some or all of them in different directories.
Use the twig.paths
option to configure those extra directories. Each path is
defined as a key: value
pair where the key
is the template directory and
the value
is the Twig namespace, which is explained later:
.. configuration-block:: .. code-block:: yaml # config/packages/twig.yaml twig: # ... paths: # directories are relative to the project root dir (but you # can also use absolute directories) 'email/default/templates': ~ 'backend/templates': ~ .. code-block:: xml <!-- config/packages/twig.xml --> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:twig="http://symfony.com/schema/dic/twig" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/twig https://symfony.com/schema/dic/twig/twig-1.0.xsd"> <twig:config> <!-- ... --> <!-- directories are relative to the project root dir (but you can also use absolute directories --> <twig:path>email/default/templates</twig:path> <twig:path>backend/templates</twig:path> </twig:config> </container> .. code-block:: php // config/packages/twig.php $container->loadFromExtension('twig', [ // ... 'paths' => [ // directories are relative to the project root dir (but you // can also use absolute directories) 'email/default/templates' => null, 'backend/templates' => null, ], ]);
When rendering a template, Symfony looks for it first in the twig.paths
directories that don't define a namespace and then falls back to the default
template directory (usually, templates/
).
Using the above configuration, if your application renders for example the
layout.html.twig
template, Symfony will first look for
email/default/templates/layout.html.twig
and backend/templates/layout.html.twig
.
If any of those templates exists, Symfony will use it instead of using
templates/layout.html.twig
, which is probably the template you wanted to use.
Twig solves this problem with namespaces, which group several templates under a logic name unrelated to their actual location. Update the previous configuration to define a namespace for each template directory:
.. configuration-block:: .. code-block:: yaml # config/packages/twig.yaml twig: # ... paths: 'email/default/templates': 'email' 'backend/templates': 'admin' .. code-block:: xml <!-- config/packages/twig.xml --> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:twig="http://symfony.com/schema/dic/twig" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/twig https://symfony.com/schema/dic/twig/twig-1.0.xsd"> <twig:config> <!-- ... --> <twig:path namespace="email">email/default/templates</twig:path> <twig:path namespace="admin">backend/templates</twig:path> </twig:config> </container> .. code-block:: php // config/packages/twig.php $container->loadFromExtension('twig', [ // ... 'paths' => [ 'email/default/templates' => 'email', 'backend/templates' => 'admin', ], ]);
Now, if you render the layout.html.twig
template, Symfony will render the
templates/layout.html.twig
file. Use the special syntax @
+ namespace to
refer to the other namespaced templates (e.g. @email/layout.html.twig
and
@admin/layout.html.twig
).
Note
A single Twig namespace can be associated with more than one template directory. In that case, the order in which paths are added is important because Twig will start looking for templates from the first defined path.
If you :ref:`install packages/bundles <symfony-flex>` in your application, they
may include their own Twig templates (in the Resources/views/
directory of
each bundle). To avoid messing with your own templates, Symfony adds bundle
templates under an automatic namespace created after the bundle name.
For example, the templates of a bundle called AcmeFooBundle
are available
under the AcmeFoo
namespace. If this bundle includes the template
<your-project>/vendor/acmefoo-bundle/Resources/views/user/profile.html.twig
,
you can refer to it as @AcmeFoo/user/profile.html.twig
.
Tip
You can also :ref:`override bundle templates <override-templates>` in case you want to change some parts of the original bundle templates.
.. toctree:: :maxdepth: 1 :glob: /templating/*