> For the complete documentation index, see [llms.txt](https://docs.thewalletcrew.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.thewalletcrew.io/developers-guides/integration-guides/wallet/liquid-templating.md).

# Liquid templating

## Liquid templating

Liquid combines fixed text with dynamic values from a pass. The Wallet Crew uses the DotLiquid implementation, plus custom date filters and the `minify` tag.

Use this reference after confirming the required data paths on a real pass.

<details>

<summary><strong>Real-world examples</strong></summary>

* A loyalty card displays a safe, whole-number points balance.
* A gift card displays its identifier, amount, and expiry.
* An event ticket turns seat data into venue directions.

</details>

### Confirm available data

Each render receives a data context. It can contain pass data, additional data, and values returned by connected providers.

Inspect **View data** on a real pass before writing a template. This payload is the source of truth for variable paths.

Additional data is suitable for visible pass content. Metadata supports operations and segmentation. It should not be treated as a wallet display source.

For the complete update and validation flow, see [Update pass data in templates](/developers-guides/integration-guides/wallet/update-pass-data-in-templates.md).

Common paths include:

| Data          | Example paths                                                             |
| ------------- | ------------------------------------------------------------------------- |
| Customer      | `firstName`, `lastName`, `address.city`                                   |
| Pass          | `serialNumber`, `tenantId`, `publicUrl`, `authenticationToken`            |
| Loyalty       | `loyalty.points`, `pendingLoyaltyPoints`, `loyalty.amounts`               |
| Gift card     | `voucher.amount`, `voucher.currency`, `id.y2.giftCardId`                  |
| Event ticket  | `ticket.name`, `ticket.startDate`, `ticket.seatNumber`, `ticket.entrance` |
| Collections   | `y2.tickets`, `y2.bons.loyaltyCertificates`                               |
| Custom values | `additionalData.<key>`                                                    |

Missing values render as an empty string. Use `default` when a visible fallback is required.

```liquid
{{ loyalty.points | default: 0 | floor }}
```

### Syntax basics

Use `{{ ... }}` to print a value. Use `{% ... %}` for logic that does not print on its own.

```liquid
Welcome {{ firstName | default: "member" }}!
{% if loyalty.points and loyalty.points > 1000 %}VIP member{% endif %}
{% assign label = ticket.priceCategory | strip %}
```

Filters run left to right. Dot notation follows the nested structure in the data payload.

#### Conditions and loops

Liquid treats only `nil` and `false` as falsy. The number `0` and the string `"false"` are truthy. Compare optional numbers explicitly.

```liquid
{% if pendingLoyaltyPoints and pendingLoyaltyPoints > 0 %}
Pending: {{ pendingLoyaltyPoints }} pts
{% endif %}
```

Guard loops with a collection-size check. This prevents empty section headings.

```liquid
{% if y2.tickets.size > 0 %}
  {% for ticket in y2.tickets %}
    - #{{ ticket.ticketNumber }} on {{ ticket.startDate | date: "D" }}
  {% endfor %}
{% endif %}
```

Inside a loop, `forloop.index`, `forloop.first`, `forloop.last`, and `forloop.length` are available.

#### Assignments, branches, and whitespace

Use `assign` to reuse calculated values. Use `case` for code-to-label mappings.

```liquid
{% assign category = ticket.priceCategory | strip %}
{% case category %}
  {% when "F" %}Full price
  {% when "R" %}Reduced
  {% else %}{{ category }}
{% endcase %}
```

Use `{%-` and `-%}` to trim surrounding whitespace. This keeps multi-line templates compact in narrow wallet fields.

```liquid
{%- assign alley = ticket.alley | strip -%}
{%- if alley -%}Alley {{ alley }}{%- endif -%}
```

#### YAML values

Quote Liquid values containing YAML special characters. Use `|-` for multi-line values.

```yaml
alternateText: 'Present at checkout: {{ id.y2.customerId }}'
value: |-
  {% assign name = firstName | append: " " | append: lastName %}
  {{ name }}
```

### Custom filters

#### Date filters

All date filters parse the input as a date or time value, apply the operation, and return a `DateTimeOffset`. Chain them with `| date: "format"` when a string output is needed.

**`add_minutes`**

Adds the specified number of minutes.

```liquid
{{ someDate | add_minutes: 60 }}
{{ someDate | add_minutes: 60 | date: "yyyy-MM-dd HH:mm:ss" }}
```

**`add_hours`**

Adds the specified number of hours.

```liquid
{{ someDate | add_hours: 24 }}
{{ someDate | add_hours: 24 | date: "yyyy-MM-dd HH:mm:ss" }}
```

**`add_seconds`**

Adds the specified number of seconds.

```liquid
{{ someDate | add_seconds: 3600 }}
{{ someDate | add_seconds: 3600 | date: "yyyy-MM-dd HH:mm:ss" }}
```

**`add_days`**

Adds the specified number of days.

```liquid
{{ someDate | add_days: 7 }}
{{ someDate | add_days: 7 | date: "yyyy-MM-dd" }}
```

**`add_timespan`**

Adds a timespan in `hh:mm:ss` format.

```liquid
{{ someDate | add_timespan: "02:30:00" }}
{{ someDate | add_timespan: "02:30:00" | date: "yyyy-MM-dd HH:mm:ss" }}
```

All date filters use invariant culture for parsing and formatting.

#### Format filter

**`format`**

Applies a format string to any `IFormattable` value such as numbers, dates, and enums.

```liquid
{{ loyaltyPoints | format: "N0" }}
```

This renders a grouped integer such as `1,234` in invariant culture.

```liquid
{{ balance | format: "C2" }}
```

This renders a currency-style value with two decimals.

### Custom tag

#### `minify`

Generates a short redirect URL from a rendered pass link.

```liquid
{% minify https://yoursite.com/account/{{ customer.id }} %}
```

The tag first renders the nested expression, then creates a short URL through The Wallet Crew redirect service, and finally outputs the shortened URL string.

### Validate and troubleshoot templates

#### A variable renders blank

A missing path does not fail the render. It resolves to an empty string. Inspect **View data** on a real pass, then use `default` where needed.

```liquid
{{ loyalty.points | default: 0 }}
```

{% hint style="warning" %}
A missing variable usually produces a blank field. Validate field paths against a real pass context before publishing a template change.
{% endhint %}

#### A date is shifted or formatted unexpectedly

Dates contain time-zone information. Format the intended output explicitly. Use `add_hours` only when a known offset is required.

```liquid
{{ ticket.startDate | date: "yyyy-MM-dd" }}
{{ ticket.startDate | add_hours: 2 | date: "HH:mm" }}
```

#### `format` ignores the pass locale

This is expected. `format` uses invariant culture. Use `date` for locale-aware dates. Assemble locale-specific number or currency text in each locale file when needed.

#### A YAML file does not parse

Quote Liquid values containing `:`, `{`, `#`, or `&`. Use `|-` for multi-line values.

#### A condition behaves unexpectedly

The number `0` and string `"false"` are truthy. Compare optional numeric values explicitly.

```liquid
{% if loyalty.points and loyalty.points > 0 %}…{% endif %}
```

#### Output has blank lines

Use whitespace-control hyphens around the tags.

```liquid
{%- if ticket.alley -%}
  Alley {{ ticket.alley }}
{%- endif -%}
```

#### Literal Liquid syntax is required

Use `raw` and `endraw`.

```liquid
{% raw %}{{ this is literal }}{% endraw %}
```

#### The template contains a syntax error

Invalid Liquid can render an error message instead of the intended value. Treat parse errors as template defects and fix them before publication.

{% hint style="info" %}
Use the pass preview in the back-office to test Liquid expressions against real pass data before deploying a template change to production.
{% endhint %}

For render timing and failure handling outside Liquid itself, see [How a pass is rendered](/developers-guides/pass-architecture/how-a-pass-is-rendered.md).

### Standard filters

Standard DotLiquid filters transform strings, numbers, dates, and collections.

#### Strings

| Filter                               | Example                                          |
| ------------------------------------ | ------------------------------------------------ |
| `default`                            | `{{ loyalty.points \| default: 0 }}`             |
| `upcase`, `downcase`, `capitalize`   | `{{ firstName \| capitalize }}`                  |
| `strip`, `lstrip`, `rstrip`          | `{{ ticket.name \| strip }}`                     |
| `append`, `prepend`                  | `{{ publicUrl \| append: tenantId }}`            |
| `replace`, `remove`                  | `{{ ticket.name \| replace: "VIP", "Premium" }}` |
| `truncate`, `truncatewords`, `slice` | `{{ id.y2.customerId \| slice: 0, 4 }}`          |
| `split`                              | `{{ "a,b,c" \| split: "," }}`                    |

Use an empty truncation suffix to create an initial:

```liquid
{{ firstName | truncate: 1, "" | upcase | append: ". " }}{{ lastName | upcase }}
```

#### Numbers and collections

| Filter                                           | Example                                                 |
| ------------------------------------------------ | ------------------------------------------------------- |
| `floor`, `ceil`, `round`                         | `{{ loyalty.points \| default: 0 \| floor }}`           |
| `plus`, `minus`, `times`, `divided_by`, `modulo` | `{{ loyalty.points \| divided_by: 100 \| floor }}`      |
| `abs`, `at_least`, `at_most`                     | `{{ balance \| at_least: 0 }}`                          |
| `size`, `first`, `last`, `join`                  | `{{ tags \| join: ", " }}`                              |
| `sort`, `reverse`, `uniq`, `compact`             | `{{ items \| sort \| reverse }}`                        |
| `map`, `where`                                   | `{{ y2.tickets \| map: "ticketNumber" \| join: ", " }}` |

#### Dates, URLs, and HTML

The `date` filter formats date values for the pass locale.

| Expression                                           | Typical output        |
| ---------------------------------------------------- | --------------------- |
| `{{ ticket.startDate \| date: "D" }}`                | `Monday, 27 May 2026` |
| `{{ ticket.startDate \| date: "d" }}`                | `27/05/2026`          |
| `{{ ticket.startDate \| date: "M" }}`                | `27 May`              |
| `{{ ticket.startDate \| date: "t" }}`                | `20:00`               |
| `{{ ticket.startDate \| date: "yyyy-MM-dd HH:mm" }}` | `2026-05-27 20:00`    |

Use `url_encode` and `url_decode` for URL values. Use `escape`, `escape_once`, or `strip_html` for HTML email values.

For the complete filter list, see the [DotLiquid reference](https://github.com/dotliquid/dotliquid/wiki/DotLiquid-for-Designers).

### Recipes

The following patterns solve common loyalty, gift-card, and event-ticket needs. Confirm all data paths before use.

#### Loyalty cards

**Display points safely**

`default` prevents a blank balance. `floor` prevents unwanted decimal points.

```liquid
{{ loyalty.points | default: 0 | floor }}
```

**Show pending points separately**

Only render the pending line when points await validation.

```liquid
Available: {{ loyalty.points | default: 0 | floor }} pts
{% if pendingLoyaltyPoints and pendingLoyaltyPoints > 0 %}
Pending: {{ pendingLoyaltyPoints }} pts
{% endif %}
```

**Create a repeating stamp counter**

`modulo` resets the visible counter after each reward.

```liquid
{{ additionalData.stampCount | default: 0 | modulo: 10 }} / 10
```

The same pattern can select a matching image:

```liquid
stamp_{{ additionalData.stampCount | default: 0 | modulo: 10 }}.png
```

**Show progress to the next reward**

Calculate the remaining points from an editable threshold.

```liquid
{% assign threshold = additionalData.next_tier_threshold | default: 100 %}
{% assign missing = threshold | minus: loyalty.points %}
{{ missing | at_least: 0 }} pts to {{ additionalData.next_tier_name | default: "your next reward" }}
```

**Derive a tier name**

This pattern works when the source provides only a points balance.

```liquid
{% assign points = loyalty.points | default: 0 %}
{% if points >= 5000 %}Gold member
{% elsif points >= 1000 %}Silver member
{% else %}Welcome member
{% endif %}
```

**List available vouchers**

Render vouchers on the back of the card. An `else` branch makes the empty state clear.

```liquid
{% if y2.bons.loyaltyCertificates.size > 0 %}
  Your vouchers:
  {% for voucher in y2.bons.loyaltyCertificates %}
    - Voucher of {{ voucher.amount }} € (expires {{ voucher.validity.endDate | date: "D" }})
  {% endfor %}
{% else %}
  No vouchers available yet.
{% endif %}
```

#### Gift cards

**Reuse one identifier**

Use the same value for the barcode and manual-entry fields.

```yaml
barCodeValue: '{{ id.y2.giftCardId }}'
barCodeAlternateText: '{{ id.y2.giftCardId }}'
cardNumber: '{{ id.y2.giftCardId }}'
```

**Display a variable amount**

This uses an optional gift amount and a safe fallback.

```yaml
value: '{{ additionalData.giftAmount | default: 50 }}'
```

**Keep amount and currency separate**

Dedicated fields let wallet platforms apply local currency formatting.

```yaml
value: '{{ voucher.amount }}'
currencyCode: '{{ voucher.currency }}'
```

**Set the validity window**

Use the source dates for expiry and relevance.

```yaml
relevantDate: '{{ startDate }}'
expirationDate: '{{ expirationDate }}'
start: '{{ startDate }}'
end: '{{ expirationDate }}'
```

#### Event tickets

**Expire after the event**

Expiry on the following day avoids time-zone and late-arrival issues.

```yaml
expirationDate: '{{ ticket.startDate | add_days: 1 | date: "d" }}'
```

**Set a relevance window**

This helps wallet platforms surface the ticket at the right time.

```yaml
start: '{{ ticket.startDate | add_days: 0 | date: "d" }}'
end: '{{ ticket.startDate | add_days: 1 | date: "d" }}'
```

**Display the event date and time**

Separate fields allow the wallet layout to prioritise date or time.

```yaml
l$label-date-apple: '{{ ticket.startDate | date: "M" }}'
l$value-date-apple: '{{ ticket.startDate | date: "t" }}'
```

**Map category codes**

`case` converts source codes into readable, locale-specific labels.

```liquid
{% assign category = ticket.priceCategory | strip %}
{% case category %}
  {% when "F" %}Full price
  {% when "R" %}Reduced
  {% when "S" %}Student
  {% when "C" %}Child
  {% else %}{{ category }}
{% endcase %}
```

**Build venue directions**

Clean optional fields before concatenating them. Whitespace control keeps the output compact.

```liquid
{%- assign zone3 = ticket.zone3 | strip -%}
{%- assign zone4 = ticket.zone4 | strip -%}
{%- assign alley = ticket.alley | strip -%}
{%- assign entrance = ticket.entrance | strip -%}
{%- assign location = zone3 | append: " " | append: zone4 -%}
{%- if alley -%}
  {%- assign location = location | append: " - Alley " | append: alley -%}
{%- endif -%}
{{ location }}
{% case entrance -%}
  {%- when "1", "2", "3", "4", "5", "6" -%}Door 1 · Elevator 2
  {%- when "7", "8" -%}Door 1 side · Elevator 1
  {%- when "9", "10", "11", "12" -%}Door 2 · Elevator 4
  {%- when "13", "14", "15", "16" -%}Door 3 · Elevator 6
{%- endcase -%}
```

**List tickets on a multi-ticket pass**

This is useful for grouped purchases and multi-event passes.

```liquid
{% if y2.tickets.size > 0 %}
  Your tickets:
  {% for ticket in y2.tickets %}
    - #{{ ticket.ticketNumber }} for {{ ticket.amount }} € on {{ ticket.creationDate | date: "D" }}
  {% endfor %}
{% endif %}
```

**Create an authenticated deep link**

Only use this pattern where the destination supports the pass authentication flow.

```liquid
Update your profile <a href="{{ publicUrl }}{{ tenantId }}/mobile?neo.authToken={{ authenticationToken }}">here</a>.
```

### FAQ

<details>

<summary><strong>Is Shopify Liquid fully supported?</strong></summary>

No. The Wallet Crew uses DotLiquid plus the custom extensions documented on this page. Shopify-specific extensions should not be assumed to work unless they are documented here.

</details>

<details>

<summary><strong>How can the correct variable path be found?</strong></summary>

Use a real pass, inspect **View data**, then validate the expression in preview. This avoids guessing field names from memory.

</details>

<details>

<summary><strong>Why does a field render blank instead of failing?</strong></summary>

Missing variables usually resolve to an empty string. Liquid syntax errors behave differently and should be treated as template defects.

</details>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.thewalletcrew.io/developers-guides/integration-guides/wallet/liquid-templating.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
