Twig Integration
Setup
use Timber\Chainsaw\Output\MarkupInterface;
use Timber\Chainsaw\Twig\ChainsawExtension;
use Twig\Runtime\EscaperRuntime;
$twig->addExtension(new ChainsawExtension($factory));
// One-liner: mark the element family html-safe so an element passed as a plain
// variable ({{ el }}) prints its markup without |raw. The |img and |picture
// filters already carry is_safe, so the common {{ image|img(alt: '…') }} case
// needs nothing; this covers pass-an-element-through-a-variable.
$twig->getRuntime(EscaperRuntime::class)
->addSafeClass(MarkupInterface::class, ['html']);
The extension accepts either an ImageFactory or a FactoryRegistry.
Pass a registry to expose named factories to img() (below); a bare factory is
treated as the registry’s single default.
Function
img(source, name?, meta?)
Create an Image from a path string. The optional second argument selects a named
factory from the FactoryRegistry —
mirroring Symfony’s asset(path, packageName) — and falls back to the default
factory when omitted or null:
{% set image = img('photo.jpg') %} {# default factory #}
{% set image = img('photo.jpg', 'cloudflare') %} {# named factory #}
{% set image = img('photo.jpg', meta: meta) %} {# explicit source metadata #}
An unregistered name throws FactoryNotFound. With a single ImageFactory (no named
factories), only the default form applies.
Pass metadata with the named argument (meta: meta) rather than positionally — the
second positional argument is the factory name, not the metadata. The Twig function
img() (which creates an Image) and the Twig filter |img (which renders one)
are separate registries; position disambiguates.
Filters
Manipulation filters
All filters accept an Image, a srcset set (WidthSet / DensitySet), or a string
(auto-coerced to Image). Arguments can be passed positionally or by name — the names
below are the filter’s named-argument vocabulary, identical to the fluent PHP API:
{{ 'photo.jpg' | width(400) | img(alt: 'Photo') }}
{{ 'photo.jpg' | crop(800, 600) | greyscale | quality(80) | img(alt: 'Photo') }}
{{ 'photo.jpg' | cover(width=800, height=600, anchor='smart') | img(alt: 'Photo') }}
{{ 'photo.jpg' | cover(800, 600, noUpscale=true) | img(alt: 'Photo') }}
The table below is machine-derived from Image’s #[Dispatchable] methods
(composer twig:generate); ? marks optional arguments.
| Filter | Arguments | Returns |
|---|---|---|
scale |
factor |
Image |
width |
width |
Image |
height |
height |
Image |
contain |
width, height, noUpscale? |
Image |
stretch |
width, height |
Image |
pad |
width, height, background?, noUpscale? |
Image |
cover |
width, height, anchor?, noUpscale? |
Image |
crop |
width, height, anchor?, noUpscale? |
Image (alias of cover) |
manualCrop |
width, height, x, y |
Image |
cropToRatio |
ratio, anchor? |
Image |
padToRatio |
ratio, background? |
Image |
autocrop |
tolerance?, color? |
Image (Image::trim()) |
blur |
radius |
Image |
sharpen |
amount |
Image |
brightness |
amount |
Image |
contrast |
amount |
Image |
gamma |
gamma |
Image |
pixelate |
size |
Image |
greyscale |
– | Image |
sepia |
– | Image |
saturation |
amount |
Image |
hue |
angle |
Image |
negate |
– | Image |
dither |
algorithm? |
Image |
blurHash |
width?, height?, componentsX?, componentsY? |
Image |
thumbHash |
width?, height? |
Image |
flip |
direction |
Image |
rotate |
degrees, background? |
Image |
autoOrient |
– | Image |
background |
color |
Image |
border |
width, unit?, color?, type? |
Image |
watermark |
source, position?, paddingX?, paddingY?, paddingUnit?, width?, height?, sizeUnit?, fit?, alpha? |
Image |
tiledWatermark |
source, width?, height?, sizeUnit?, fit?, alpha?, gapX?, gapY?, gapUnit? |
Image |
dpr |
dpr |
Image |
to |
format |
Image (Image::format()) |
quality |
quality |
Image |
preset |
name |
Image |
widths |
...widths |
WidthSet |
densities |
...ratios |
DensitySet |
For the anchor arguments, pass a compass string ('center', 'top', 'bottom-left',
…), a detection keyword ('smart', 'entropy', 'attention', 'face'), or
'object:<class>'. For ratio, pass a string like '16:9'.
For to, pass the format string: 'webp', 'avif', 'jpg', 'png', 'gif'.
Every manipulation has a dedicated filter except without() — its argument is a
manipulator class-string, awkward in a template, so it stays on the manipulate hash
filter below ({{ image | manipulate({without: '...'}) }}).
manipulate
Apply operations from a hash:
{{ 'photo.jpg' | manipulate({ width: 400, blur: 10, quality: 80 }) | img(alt: 'Photo') }}
img (output filter)
The presentation portal: turn an Image or set into an <img>. Its arguments mirror
Image::img() — alt (required), then layout, sizes, priority, loading,
decoding, fetchPriority, placeholder, inlineStyle, attrs. The filter is
marked safe, so it prints raw:
{# <img> tag #}
{{ image | img(alt: 'Photo') }}
{{ image | img(alt: 'Photo', priority: true, attrs: { class: 'hero' }) }}
{# a layout derives the srcset + sizes #}
{{ image | img(alt: 'Photo', layout: 'constrained') }}
{# an explicit srcset #}
{{ image | widths(800, 400) | img(alt: 'Photo', sizes: '100vw') }}
{# a blur placeholder #}
{{ image | img(alt: 'Photo', placeholder: 'thumbhash') }}
See Layout & Sizes for layout / sizes, and
Placeholders for placeholder.
picture
Declarative responsive <picture>. alt comes first, then the media-keyed source
map and the format list; the remaining img() intent arguments follow. Also safe:
{{ 'hero.jpg' | picture(
alt: 'Hero',
sources: {
md: { cover: [960, 640], widths: [960, 1920], sizes: '50vw' },
max-md: { cover: [640, 640] },
},
formats: ['webp', 'avif'],
) }}
Each source entry is a manipulation op map built from the pristine root; the reserved
sizes key is lifted out before dispatch, and an unknown op key throws. Media keys
accept a screens name (md, max-md), a CSS
media type, or a raw query. See Art direction.
Full example
{# Responsive image — layout derives the srcset and sizes #}
{{ img('hero.jpg') | cover(1200, 600) | img(alt: 'Hero banner', layout: 'constrained', priority: true) }}
{# Explicit widths + sizes #}
{{ img('hero.jpg') | cover(1200, 600) | widths(1200, 800, 400) | img(
alt: 'Hero banner',
sizes: '(min-width: 1200px) 1200px, 100vw',
) }}
{# Thumbnail preset #}
{{ img('avatar.jpg') | preset('thumbnail') | img(alt: user.name) }}
{# Greyscale with WebP <picture> #}
{{ img('photo.jpg') | crop(600, 400) | greyscale | quality(80) | picture(alt: 'Photo', formats: ['webp']) }}
The filters dispatch through the renderers configured on the ImageFactory (defaults:
HtmlImgRenderer, HtmlSourceRenderer, HtmlPictureRenderer). To customize the
markup, supply your own renderer to the factory — see Rendering.