Skip to main content

Conditional Content and Loops in Engagement Letters

Use Liquid's if/else and for tags to make one engagement letter template adapt to every engagement — showing optional clauses, branching on form answers, and listing every contact or owner.

Written by Blaine Korte

One template can serve many situations: include the rush clause only when the client asked for rush service, list every business owner the form collected, show a payment schedule only for recurring services. Liquid's logic tags — {% if %} and {% for %} — make your engagement letter adapt to each engagement.

Showing and hiding content with if

Wrap any content in an if block and it only appears when the condition is true:

{% if engagement.rush == "Yes" %}
Because you have requested expedited service, a rush fee of $500 will
be added to the fees described above.
{% endif %}

Add an {% else %} for the alternative, or {% elsif %} for several branches:

{% if engagement.entity_type == "LLC" %}
We will prepare and file Articles of Organization.
{% elsif engagement.entity_type == "Corporation" %}
We will prepare and file Articles of Incorporation.
{% else %}
We will advise you on the appropriate entity type before filing.
{% endif %}

{% unless %} is the mirror image — the content shows when the condition is false.

What you can test

Condition

True when…

engagement.rush == "Yes"

the answer is exactly "Yes"

engagement.rush != "Yes"

the answer is anything else

engagement.add_ons contains "Trademark Search"

that option was among the checkbox selections

client.phone != blank

the client has a phone number

signers.spouse.skipped == false

the optional signer wasn't skipped

Combine conditions with and / or:

{% if engagement.entity_type == "LLC" and engagement.state == "Delaware" %}

Three practical rules

  1. Compare against the label. Radio and checkbox answers print the option labels your client saw — so test against those exact labels, including capitalization: == "Yes", not == "yes".

  2. Use straight quotes. Word and Google Docs love converting straight quotes into curly ones, which breaks the comparison. Undo the autocorrect (Ctrl/Cmd-Z right after typing) or disable smart quotes while editing.

  3. Close every block. Each {% if %} needs its {% endif %} — an unbalanced block stops the letter from generating, and the error will tell you which tag is unmatched.

Whole paragraphs vs. inline conditions

Both styles work:

  • Inline — hide part of a line: Fee: {{ service.price | usd }}{% if service.frequency != blank %} per {{ service.frequency }}{% endif %}

  • Whole paragraph — put {% if %} on its own line above the content and {% endif %} on its own line below. When the condition is false, the paragraphs are removed cleanly — no stray empty lines left behind.

For multi-paragraph sections (a whole optional clause), always use the whole-paragraph style.

Repeating content with for

Loops repeat a block once per item in a list. The classic example is listing every contact on the engagement:

{% for user in users %}{{ user.name }}, {{ user.email }}
{% endfor %}

The name after for (here user) is yours to choose; use it inside the loop to refer to the current item.

Signers collected from a repeating form group ("add each business owner") work the same way — see Placing Signatures in Engagement Letters for signature loops:

{% for owner in signers.owners %}
{% signature owner %}
{% endfor %}

Differences between Word and Google Docs templates

Both formats support everything above, with two caveats for Google Docs:

  • Keep each tag uniformly styled. A tag like {% if engagement.rush == "Yes" %} must be one consistent style — don't bold half of it. (Style the content between tags however you like.)

  • Loop bodies stay within one paragraph. In Google Docs, a {% for %} block can't span multiple paragraphs or repeat table rows. Word templates don't have this limit — and Word additionally supports repeating table rows (see Advanced Liquid).

Testing your logic

Generate a test letter for each important path — one with "Yes", one with "No" — and read the output. Conditional bugs are much easier to spot in a rendered letter than in the template.

Did this answer your question?