This one is ugly, but at least there is a current workaround.
Fluid Field metadata conditionals fail when the current item is a Relationship field
Minimal reproduction
Create a Fluid field with the short name fluid_field containing these two ungrouped fields:
- A Text field named
my_text.
- A Relationship field named
my_related.
Add both fields to an entry in this order:
Then for the tag:
{fluid_field}
{fluid_field:my_text}
<div class="text">
{content}
</div>
{/fluid_field:my_text}
{fluid_field:my_related}
<ul class="related-entries">
{content status="open"}
<li>{content:title}</li>
{/content}
</ul>
{/fluid_field:my_related}
{if fluid_field:last}
<p>LAST FLUID ITEM</p>
{/if}
{/fluid_field}
Expected result
Because my_related is the final item in the Fluid field, this conditional should evaluate as true:
{if fluid_field:last}
<p>LAST FLUID ITEM</p>
{/if}
The rendered output should include:
Actual result
The Relationship field content renders, but the fluid_field:last conditional does not render its contents.
If any additional Fluid item is added after the Relationship field—even an unrelated fieldtype—the conditional works when that new non-Relationship item becomes the last item.
This makes the behavior appear data-dependent, but it is actually dependent on the fieldtype occupying the relevant Fluid position.
Template workaround
The immediate workaround is to output the Fluid metadata variable as a brace-wrapped single tag inside the conditional. The docs note this is necessary for metadata variables using a parameter, but it's a problem regardless of that when you have that weird last relationship field thing going on. In any case- the workaround is bracing your variables.
{fluid_field}
{fluid_field:my_text}
<div class="text">
{content}
</div>
{/fluid_field:my_text}
{fluid_field:my_related}
<ul class="related-entries">
{content status="open"}
<li>{content:title}</li>
{/content}
</ul>
{/fluid_field:my_related}
{if '{fluid_field:last}' == '1'}
<p>LAST FLUID ITEM</p>
{/if}
{/fluid_field}
In other words, replace:
with:
{if '{fluid_field:last}' == '1'}
Why the error occurs
The Fluid parser correctly calculates the metadata for each Fluid item.
In:
system/ee/legacy/libraries/Fluid_field_parser.php
the parser constructs metadata including:
$meta = [
$fluid_field_name . ':first' => (int) ($g == 0 && $firstInGroup),
$fluid_field_name . ':last' => (int) (($g + 1) == $total_groups && $lastInGroup),
$fluid_field_name . ':count' => $i + 1,
$fluid_field_name . ':index' => $i,
// ...
];
The Relationship field is therefore correctly assigned a last value of 1 when it is the final Fluid item.
The problem occurs later in:
system/ee/ExpressionEngine/Addons/fluid_field/Service/Tag.php
Tag::parse() has a special execution path for Relationship fields:
if ($field->getType() == 'relationship') {
// Delegate Relationship content to the Relationship parser.
$tagdata = $relationship_parser->parse(
$field->getContentId(),
$tagdata,
$channel
);
$tagdata = $field->replaceTag($tagdata);
$field->setName($name);
return $tagdata;
}
That branch returns before reaching the normal Fluid conditional processing:
$tagdata = $this->parseConditionals($field, $tagdata, $meta);
For non-Relationship fields, the Fluid metadata array is passed to parseConditionals(). This allows a conditional such as:
to be evaluated using the calculated fluid_field:last value.
For Relationship fields, the method returns before that step. The Relationship parser handles the related-entry tags, but the current Fluid metadata is not passed through the normal Fluid conditional-processing path.
This explains why adding another field appears to fix the problem: the new final field follows the normal parsing path, so its last conditional is evaluated.
Other metadata potentially affected
The problem is not limited to last. It can potentially affect any Fluid metadata used directly as a conditional variable while the current Fluid item is a Relationship field.
The documented Fluid metadata includes:
Grouped Fluid fields also provide metadata including:
first_group
last_group
- etc..
These variables and the grouped syntax are documented in the ExpressionEngine Fluid Field documentation.
Possible symptoms include:
- Opening markup not rendering when a Relationship field is first.
- Closing markup not rendering when a Relationship field is last.
- Count- or index-based classes being omitted.
- Previous/next field logic failing.
- Separators between Fluid items disappearing.
- Group wrappers becoming unbalanced.
- A Fluid field containing only one Relationship item failing both
first and last conditionals.
- Output changing when an unrelated Fluid item is added or reordered.
Standalone output of the metadata may still work:
Last value: {fluid_field:last}
The failure concerns using the metadata directly as a conditional variable:
Why the workaround works
Tag::parse() begins by replacing brace-wrapped metadata tags:
$tagdata = $this->replaceMetaTags($meta);
That replacement happens before the Relationship-specific branch.
Consequently, this conditional:
{if '{fluid_field:last}' == '1'}
is converted to something equivalent to:
before the Relationship field delegates its content to the Relationship parser and returns early.
By contrast, this form:
does not contain a standalone {fluid_field:last} tag for replaceMetaTags() to replace. It depends on the later call to parseConditionals(), which the Relationship branch skips.
The workaround therefore forces the metadata substitution to occur during the portion of Tag::parse() that both normal fields and Relationship fields execute.
The same pattern can be used for other Fluid metadata:
{if '{fluid_field:first}' == '1'}
First item
{/if}
{if '{fluid_field:count}' == '2'}
Second item
{/if}
{if '{fluid_field:current_field_name}' == 'my_related'}
Current item is the Relationship field
{/if}
{if '{fluid_field:next_field_name}' == ''}
There is no next item
{/if}
Parameterized metadata can use the same explicit comparison:
{if '{fluid_field:last name="my_related"}' == '1'}
This is the last my_related field
{/if}
Grouped versus ungrouped Fluid fields
The ungrouped example should be used as the primary reproduction because it isolates the problem without introducing group parsing.
However, grouped Fluid fields appear susceptible to the same underlying issue. ExpressionEngine ultimately parses each child field in a group using a FieldFacade. If a child field is a Relationship field, it follows the same Relationship-specific branch in Tag::parse().
For example:
{fluid_field}
{fluid_field:my_group}
{fields}
{fluid_field:my_text}
{content}
{/fluid_field:my_text}
{fluid_field:my_related}
{content status="open"}
{content:title}
{/content}
{/fluid_field:my_related}
{if fluid_field:last_in_group}
LAST FIELD IN GROUP
{/if}
{/fields}
{/fluid_field:my_group}
{/fluid_field}
If my_related is the last child field in the group, the same early-return path may prevent the direct last_in_group conditional from receiving Fluid metadata.
The corresponding workaround is:
{if '{fluid_field:last_in_group}' == '1'}
LAST FIELD IN GROUP
{/if}
ExpressionEngine 7.5 also supports accessing grouped fields without a {fields} loop by using group-prefixed field names. That syntax should receive separate regression coverage because its tag extraction differs, although Relationship child fields ultimately reach the same special parsing branch.
Suggested technical approach
A minimal fix would be to process the remaining Fluid metadata conditionals after Relationship parsing but before returning from the Relationship branch:
if ($field->getType() == 'relationship') {
// Existing Relationship parsing...
$tagdata = $field->replaceTag($tagdata);
// Evaluate remaining outer Fluid conditionals using Fluid metadata.
$tagdata = $this->parseConditionals($field, $tagdata, $meta);
$field->setName($name);
return $tagdata;
}
The ordering matters. Calling the general Fluid conditional parser before the Relationship parser could prematurely evaluate or remove conditionals that depend on related-entry data.
A more targeted implementation would identify Fluid metadata variables in conditional expressions and replace only those variables with their values before delegating to the Relationship parser. That would avoid evaluating unrelated Relationship conditionals in the wrong context.
This one is ugly, but at least there is a current workaround.
Fluid Field metadata conditionals fail when the current item is a Relationship field
Minimal reproduction
Create a Fluid field with the short name
fluid_fieldcontaining these two ungrouped fields:my_text.my_related.Add both fields to an entry in this order:
Then for the tag:
{fluid_field} {fluid_field:my_text} <div class="text"> {content} </div> {/fluid_field:my_text} {fluid_field:my_related} <ul class="related-entries"> {content status="open"} <li>{content:title}</li> {/content} </ul> {/fluid_field:my_related} {if fluid_field:last} <p>LAST FLUID ITEM</p> {/if} {/fluid_field}Expected result
Because
my_relatedis the final item in the Fluid field, this conditional should evaluate as true:{if fluid_field:last} <p>LAST FLUID ITEM</p> {/if}The rendered output should include:
Actual result
The Relationship field content renders, but the
fluid_field:lastconditional does not render its contents.If any additional Fluid item is added after the Relationship field—even an unrelated fieldtype—the conditional works when that new non-Relationship item becomes the last item.
This makes the behavior appear data-dependent, but it is actually dependent on the fieldtype occupying the relevant Fluid position.
Template workaround
The immediate workaround is to output the Fluid metadata variable as a brace-wrapped single tag inside the conditional. The docs note this is necessary for metadata variables using a parameter, but it's a problem regardless of that when you have that weird last relationship field thing going on. In any case- the workaround is bracing your variables.
{fluid_field} {fluid_field:my_text} <div class="text"> {content} </div> {/fluid_field:my_text} {fluid_field:my_related} <ul class="related-entries"> {content status="open"} <li>{content:title}</li> {/content} </ul> {/fluid_field:my_related} {if '{fluid_field:last}' == '1'} <p>LAST FLUID ITEM</p> {/if} {/fluid_field}In other words, replace:
{if fluid_field:last}with:
{if '{fluid_field:last}' == '1'}Why the error occurs
The Fluid parser correctly calculates the metadata for each Fluid item.
In:
the parser constructs metadata including:
The Relationship field is therefore correctly assigned a
lastvalue of1when it is the final Fluid item.The problem occurs later in:
Tag::parse()has a special execution path for Relationship fields:That branch returns before reaching the normal Fluid conditional processing:
For non-Relationship fields, the Fluid metadata array is passed to
parseConditionals(). This allows a conditional such as:{if fluid_field:last}to be evaluated using the calculated
fluid_field:lastvalue.For Relationship fields, the method returns before that step. The Relationship parser handles the related-entry tags, but the current Fluid metadata is not passed through the normal Fluid conditional-processing path.
This explains why adding another field appears to fix the problem: the new final field follows the normal parsing path, so its
lastconditional is evaluated.Other metadata potentially affected
The problem is not limited to
last. It can potentially affect any Fluid metadata used directly as a conditional variable while the current Fluid item is a Relationship field.The documented Fluid metadata includes:
firstlastGrouped Fluid fields also provide metadata including:
first_grouplast_groupThese variables and the grouped syntax are documented in the ExpressionEngine Fluid Field documentation.
Possible symptoms include:
firstandlastconditionals.Standalone output of the metadata may still work:
Last value: {fluid_field:last}The failure concerns using the metadata directly as a conditional variable:
{if fluid_field:last}Why the workaround works
Tag::parse()begins by replacing brace-wrapped metadata tags:That replacement happens before the Relationship-specific branch.
Consequently, this conditional:
{if '{fluid_field:last}' == '1'}is converted to something equivalent to:
{if '1' == '1'}before the Relationship field delegates its content to the Relationship parser and returns early.
By contrast, this form:
{if fluid_field:last}does not contain a standalone
{fluid_field:last}tag forreplaceMetaTags()to replace. It depends on the later call toparseConditionals(), which the Relationship branch skips.The workaround therefore forces the metadata substitution to occur during the portion of
Tag::parse()that both normal fields and Relationship fields execute.The same pattern can be used for other Fluid metadata:
{if '{fluid_field:first}' == '1'} First item {/if} {if '{fluid_field:count}' == '2'} Second item {/if} {if '{fluid_field:current_field_name}' == 'my_related'} Current item is the Relationship field {/if} {if '{fluid_field:next_field_name}' == ''} There is no next item {/if}Parameterized metadata can use the same explicit comparison:
{if '{fluid_field:last name="my_related"}' == '1'} This is the last my_related field {/if}Grouped versus ungrouped Fluid fields
The ungrouped example should be used as the primary reproduction because it isolates the problem without introducing group parsing.
However, grouped Fluid fields appear susceptible to the same underlying issue. ExpressionEngine ultimately parses each child field in a group using a
FieldFacade. If a child field is a Relationship field, it follows the same Relationship-specific branch inTag::parse().For example:
{fluid_field} {fluid_field:my_group} {fields} {fluid_field:my_text} {content} {/fluid_field:my_text} {fluid_field:my_related} {content status="open"} {content:title} {/content} {/fluid_field:my_related} {if fluid_field:last_in_group} LAST FIELD IN GROUP {/if} {/fields} {/fluid_field:my_group} {/fluid_field}If
my_relatedis the last child field in the group, the same early-return path may prevent the directlast_in_groupconditional from receiving Fluid metadata.The corresponding workaround is:
{if '{fluid_field:last_in_group}' == '1'} LAST FIELD IN GROUP {/if}ExpressionEngine 7.5 also supports accessing grouped fields without a
{fields}loop by using group-prefixed field names. That syntax should receive separate regression coverage because its tag extraction differs, although Relationship child fields ultimately reach the same special parsing branch.Suggested technical approach
A minimal fix would be to process the remaining Fluid metadata conditionals after Relationship parsing but before returning from the Relationship branch:
The ordering matters. Calling the general Fluid conditional parser before the Relationship parser could prematurely evaluate or remove conditionals that depend on related-entry data.
A more targeted implementation would identify Fluid metadata variables in conditional expressions and replace only those variables with their values before delegating to the Relationship parser. That would avoid evaluating unrelated Relationship conditionals in the wrong context.