How to Write a custom Twig Extension

How to Write a custom Twig Extension

Twig Extensions allow to create custom functions, filters and more to use them in your Twig templates. Before writing your own Twig extension, check if the filter/function that you need is already implemented in:

Create the Extension Class

Suppose you want to create a new filter called price that formats a number into money:

  1. {{ product.price|price }}
  2. {# pass in the 3 optional arguments #}
  3. {{ product.price|price(2, ',', '.') }}

Create a class that extends AbstractExtension and fill in the logic:

  1. // src/Twig/AppExtension.php
  2. namespace App\Twig;
  3. use Twig\Extension\AbstractExtension;
  4. use Twig\TwigFilter;
  5. class AppExtension extends AbstractExtension
  6. {
  7. public function getFilters()
  8. {
  9. return [
  10. new TwigFilter('price', [$this, 'formatPrice']),
  11. ];
  12. }
  13. public function formatPrice($number, $decimals = 0, $decPoint = '.', $thousandsSep = ',')
  14. {
  15. $price = number_format($number, $decimals, $decPoint, $thousandsSep);
  16. $price = '$'.$price;
  17. return $price;
  18. }
  19. }

If you want to create a function instead of a filter, define the getFunctions() method:

  1. // src/Twig/AppExtension.php
  2. namespace App\Twig;
  3. use Twig\Extension\AbstractExtension;
  4. use Twig\TwigFunction;
  5. class AppExtension extends AbstractExtension
  6. {
  7. public function getFunctions()
  8. {
  9. return [
  10. new TwigFunction('area', [$this, 'calculateArea']),
  11. ];
  12. }
  13. public function calculateArea(int $width, int $length)
  14. {
  15. return $width * $length;
  16. }
  17. }

Tip

Along with custom filters and functions, you can also register global variables.

Register an Extension as a Service

Next, register your class as a service and tag it with twig.extension. If you’re using the default services.yaml configuration, you’re done! Symfony will automatically know about your new service and add the tag.

You can now start using your filter in any Twig template. Optionally, execute this command to confirm that your new filter was successfully registered:

  1. # display all information about Twig
  2. $ php bin/console debug:twig
  3. # display only the information about a specific filter
  4. $ php bin/console debug:twig --filter=price

Creating Lazy-Loaded Twig Extensions

New in version 1.35: Support for lazy-loaded extensions was introduced in Twig 1.35.0 and 2.4.4.

Including the code of the custom filters/functions in the Twig extension class is the simplest way to create extensions. However, Twig must initialize all extensions before rendering any template, even if the template doesn’t use an extension.

If extensions don’t define dependencies (i.e. if you don’t inject services in them) performance is not affected. However, if extensions define lots of complex dependencies (e.g. those making database connections), the performance loss can be significant.

That’s why Twig allows to decouple the extension definition from its implementation. Following the same example as before, the first change would be to remove the formatPrice() method from the extension and update the PHP callable defined in getFilters():

  1. // src/Twig/AppExtension.php
  2. namespace App\Twig;
  3. use App\Twig\AppRuntime;
  4. use Twig\Extension\AbstractExtension;
  5. use Twig\TwigFilter;
  6. class AppExtension extends AbstractExtension
  7. {
  8. public function getFilters()
  9. {
  10. return [
  11. // the logic of this filter is now implemented in a different class
  12. new TwigFilter('price', [AppRuntime::class, 'formatPrice']),
  13. ];
  14. }
  15. }

Then, create the new AppRuntime class (it’s not required but these classes are suffixed with Runtime by convention) and include the logic of the previous formatPrice() method:

  1. // src/Twig/AppRuntime.php
  2. namespace App\Twig;
  3. use Twig\Extension\RuntimeExtensionInterface;
  4. class AppRuntime implements RuntimeExtensionInterface
  5. {
  6. public function __construct()
  7. {
  8. // this simple example doesn't define any dependency, but in your own
  9. // extensions, you'll need to inject services using this constructor
  10. }
  11. public function formatPrice($number, $decimals = 0, $decPoint = '.', $thousandsSep = ',')
  12. {
  13. $price = number_format($number, $decimals, $decPoint, $thousandsSep);
  14. $price = '$'.$price;
  15. return $price;
  16. }
  17. }

If you’re using the default services.yaml configuration, this will already work! Otherwise, create a service for this class and tag your service with twig.runtime.

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.