You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
The default filter should only evaluate the expression passed to it if necessary #4668
Not sure if this is a bug or just unexpected behavior, but I just found that the expression passed to the default filter will always evaluate the expression that is passed to it, even when it's not required.
In this example, from a template developer's perspective, I would expect that some_expensive_function() is only called if bar is falsy. However, it's always called, no matter the value of bar.
This is a problem when the parameter is expensive to compute or requires a database call. For example, in Craft CMS template, I often do something like this:
{% setimage=image | default(entry.image.one()) %}
This will either take the image passed to the template, or load a fallback image from the database. I only want to run the additional database query when image is empty.
I can use null coalescing, which behaves differently. But sometimes I want the default filter, to also handle cases where the first variable may be falsy, but not null.
I don't know how complicated this change would be. At least in theory, I don't see any reason why the default filter couldn't use short-circuiting in its generated PHP code to avoid running the expression unnecessarily.
While it may be "intended" that the expression is evaluated, I personally also never thought of it, and been putting values/expressions into the |default filter as if it was a replacement for ?? or defined/empty tests with little regard to what's actually going in there.
I really think handling of null, empty or other falsly values could be vastly improved for Twig. If not through |default, then perhaps something new. Once the dev gets to the template, they shouldn't have to really think about the types anymore, and a very simple, concise, in-line way to say "falsly or this value" would definitely be appreciated.
You could probably accomplish this is another way, like with a ternary condition:
In this case the example is quite easy, but imagine if you need to test several levels deep, where each level could be null or even "undefined":
Although this would be a BC break, since passing a callable into |default is something you can already do now, and rn you'd expect the callable to just be returned, not executed..
A non BC solution would be a special parser that takes |default, and wraps it in a function that only gets called if the lhs is undefined/null/falsly.. Or a new filter/control structure altogether?
truthy or this value is covered by the ?: operator, which short-circuits properly. non-null or this value is covered by the ?? operator (which handles undefined values gracefully), which also short-circuits.
In practice, I almost never use the default filter anymore (which is unsafe to use on boolean values, as foo.isMatching|default(true) will actually never be false as the empty test considers false as empty)
Activity
gerryvdm commentedon Jan 8, 2026
This looks like expected behaviour that the filter arguments are eagerly evaluated. Filters are not control structures.
You could probably accomplish this is another way, like with a ternary condition:
set image = image is defined and image is not empty ? image : entry.image.one()jannes-io commentedon Jan 8, 2026
While it may be "intended" that the expression is evaluated, I personally also never thought of it, and been putting values/expressions into the
|defaultfilter as if it was a replacement for??ordefined/emptytests with little regard to what's actually going in there.I really think handling of
null,emptyor other falsly values could be vastly improved for Twig. If not through|default, then perhaps something new. Once the dev gets to the template, they shouldn't have to really think about the types anymore, and a very simple, concise, in-line way to say "falsly or this value" would definitely be appreciated.In this case the example is quite easy, but imagine if you need to test several levels deep, where each level could be
nullor even "undefined":{{ foo.bar.baz|default(expensive_expr()) }}Would become
{% set thing = foo is defined and foo is not empty ? (foo.bar is defined and foo.bar is not empty ? (foo.bar.baz is defined and foo.bar.baz is not empty ? foo.bar.baz : expensive_expr()) : expensive_expr()) : expensive_expr() %}Perhaps we should allow passing a callable into
|defaultthat is executed, so we can write something like:{% set thing = foo.bar.baz|default(() => expensive_expr()) %}Although this would be a BC break, since passing a callable into
|defaultis something you can already do now, and rn you'd expect the callable to just be returned, not executed..A non BC solution would be a special parser that takes
|default, and wraps it in a function that only gets called if the lhs is undefined/null/falsly.. Or a new filter/control structure altogether?stof commentedon Sep 25, 2026
truthy or this valueis covered by the?:operator, which short-circuits properly.non-null or this valueis covered by the??operator (which handles undefined values gracefully), which also short-circuits.In practice, I almost never use the
defaultfilter anymore (which is unsafe to use on boolean values, asfoo.isMatching|default(true)will actually never befalseas theemptytest considersfalseas empty)