> ## Documentation Index
> Fetch the complete documentation index at: https://docs.conversion.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Liquid Overview

> Create dynamic, personalized content using Liquid syntax in emails, workflows, and webhooks.

[Liquid](https://shopify.github.io/liquid/) is an open-source template language that enables you to create dynamic, personalized content throughout Conversion. Use Liquid to insert contact data, add conditional logic, and transform values in your emails, workflows, and integrations.

## Quick Start

Liquid uses simple syntax to output dynamic values:

```liquid theme={null}
Hi {{ contact.first_name }}, welcome to {{ company.name }}!
```

This outputs: "Hi Sarah, welcome to Acme Corp!"

***

## Where You Can Use Liquid

Liquid is supported throughout Conversion:

| Location              | Supported Fields                                                                    |
| :-------------------- | :---------------------------------------------------------------------------------- |
| **Emails**            | Subject line, preview, and body content                                             |
| **Workflow nodes**    | Magic enrich, Slack messages, internal email alerts, Salesforce task creation, etc. |
| **Change Field node** | The value being set                                                                 |
| **Webhooks**          | Request body                                                                        |

***

## Liquid Basics

Liquid consists of three core components:

### Objects

Objects output dynamic values using double curly braces:

```liquid theme={null}
{{ contact.first_name }}
{{ company.industry }}
```

### Tags

Tags create logic and control flow using curly braces with percent signs:

```liquid theme={null}
{% if contact.industry == "Technology" %}
  Welcome to our tech newsletter!
{% endif %}
```

### Filters

Filters modify output using the pipe character:

```liquid theme={null}
{{ contact.first_name | upcase }}
{{ contact.created_at | date: "%B %d, %Y" }}
```

<Tip>
  Conversion supports all standard Liquid syntax. See [Shopify's Liquid documentation](https://shopify.github.io/liquid/) for the complete language reference.
</Tip>

***

## Data Objects

All Liquid in Conversion is written from the **contact's perspective**. This provides a consistent mental model: you're always asking "what data does this contact have access to?"

### Contact

Access any field on the contact:

```liquid theme={null}
{{ contact.first_name }}
{{ contact.email }}
{{ contact.job_title }}
{{ contact.custom_field_key }}
```

### Company

Access fields from the contact's associated company:

```liquid theme={null}
{{ company.name }}
{{ company.industry }}
{{ company.annual_revenue }}
```

### Opportunities

Access opportunities through two paths, depending on the relationship:

<Tabs>
  <Tab title="Company Opportunities">
    Access opportunities associated with the contact's company. Returns up to 10 opportunities, sorted by their created date in the CRM in descending order — newest first.

    The numeric index reflects creation order: `[0]` is the most recently created opportunity and `[9]` is the oldest of the 10 returned. Use the index to target a specific opportunity by recency:

    ```liquid theme={null}
    {{ company._opportunities[0].name }}
    {{ company._opportunities[0].amount }}
    {{ company._opportunities[0].stage }}
    ```

    Use `.first` and `.last` as shortcuts. `.first` is equivalent to `[0]` (the most recently created opportunity). `.last` returns the last item in the array (the oldest of the 10 returned — not necessarily the oldest opportunity overall):

    ```liquid theme={null}
    {{ company._opportunities.first.name }}
    {{ company._opportunities.last.stage }}
    ```

    **Iterate over all opportunities:**

    ```liquid theme={null}
    {% for opp in company._opportunities %}
      - {{ opp.name }}: {{ opp.stage }}
    {% endfor %}
    ```

    **Example: reference the most recent opportunity in a Slack message**

    A common pattern is alerting a Slack channel about the contact's most recently created opportunity:

    ```liquid theme={null}
    New activity on {{ company.name }}'s latest opportunity:
    *{{ company._opportunities.first.name }}* — {{ company._opportunities.first.stage }}
    Amount: {{ company._opportunities.first.amount }}
    Owner: {{ company._opportunities.first.owner_name | default: "Unassigned" }}
    ```

    **Access the contact's role on a company opportunity:**

    If the contact has an Opportunity Contact Role (OCR) on an opportunity:

    ```liquid theme={null}
    {{ company._opportunities.first._role.role }}
    {{ company._opportunities.first._role.is_primary }}
    ```
  </Tab>

  <Tab title="Contact Opportunity Roles">
    Access opportunities where the contact has a direct Opportunity Contact Role. Returns the 10 most recently created OCRs.

    ```liquid theme={null}
    {{ opportunity_roles.first.role }}
    {{ opportunity_roles.first.is_primary }}
    {{ opportunity_roles.first._opportunity.name }}
    {{ opportunity_roles.first._opportunity.amount }}
    ```

    **Iterate over opportunity roles:**

    ```liquid theme={null}
    {% for ocr in opportunity_roles %}
      {% if ocr.is_primary %}
        Primary contact on {{ ocr._opportunity.name }}
      {% endif %}
    {% endfor %}
    ```
  </Tab>
</Tabs>

### Custom Objects

Access custom objects related to the contact. Each custom object type returns the 10 most recently created objects.

```liquid theme={null}
{{ objects.product[0].name }}
{{ objects.product[0].sku }}
{{ objects.subscription.first.plan_name }}
```

**Reserved fields** available on all custom objects:

| Field        | Description                      |
| :----------- | :------------------------------- |
| `id`         | Unique identifier                |
| `type`       | The custom object type name      |
| `created_at` | When the object was created      |
| `updated_at` | When the object was last updated |

**Access relationship fields** (metadata about the connection between the contact and object):

```liquid theme={null}
{{ objects.product[0]._relationship.role }}
{{ objects.product[0]._relationship.assigned_date }}
```

**Iterate over custom objects:**

```liquid theme={null}
{% for product in objects.product %}
  - {{ product.name }} (SKU: {{ product.sku }})
{% endfor %}
```

### Tokens

Access token values set in your campaigns:

```liquid theme={null}
{{ tokens.webinar_date }}
{{ tokens.promotion_code }}
```

<Tip>
  The campaign referenced is the **parent campaign of the asset** you are using Liquid in. For example, using `tokens.webinar_date` in a blast will reference the value of `webinar_date` of the campaign the blast is in.
</Tip>

### Current Date and Time

Use `now` to get the current UTC datetime:

```liquid theme={null}
{{ now | date: "%B %d, %Y" }}
{{ now | date: "%Y-%m-%d" }}
```

***

## Trigger Context

When a workflow runs, contextual data about what triggered it is available through the `trigger` object. Use `trigger.type` to identify what triggered the workflow.

### Form Submissions

```liquid theme={null}
{% if trigger.type == "form_submission" %}
  Thanks for submitting {{ trigger.form_submission.form_name }}!
{% endif %}
```

| Field                                    | Description                      |
| :--------------------------------------- | :------------------------------- |
| `trigger.form_submission.form_name`      | Name of the form                 |
| `trigger.form_submission.form_id`        | Unique identifier of the form    |
| `trigger.form_submission.page_url`       | URL where the form was submitted |
| `trigger.form_submission.submitted_at`   | Timestamp of submission          |
| `trigger.form_submission.fields.<field>` | Any submitted field value        |

```liquid theme={null}
{{ trigger.form_submission.fields.company_size }}
{{ trigger.form_submission.fields.use_case }}
```

<Tip>
  If a form field name contains spaces (e.g., "Country Of Origin"), you can't use dot notation. Use bracket syntax instead:

  ```liquid theme={null}
  {{ trigger.form_submission.fields["Country Of Origin"] }}
  ```
</Tip>

### Page Visits

```liquid theme={null}
{% if trigger.type == "page_visit" %}
  Thanks for visiting {{ trigger.page_visit.page_url }}
{% endif %}
```

| Field                             | Description             |
| :-------------------------------- | :---------------------- |
| `trigger.page_visit.page_url`     | URL of the visited page |
| `trigger.page_visit.referrer`     | Referring URL           |
| `trigger.page_visit.utm_source`   | UTM source parameter    |
| `trigger.page_visit.utm_medium`   | UTM medium parameter    |
| `trigger.page_visit.utm_campaign` | UTM campaign parameter  |
| `trigger.page_visit.visited_at`   | Timestamp of visit      |

### Email Events

All email triggers share common metadata:

| Field                   | Description                     |
| :---------------------- | :------------------------------ |
| `trigger.email.id`      | Email asset ID                  |
| `trigger.email.name`    | Name of the email in Conversion |
| `trigger.email.sent_at` | Timestamp when email was sent   |

**For link clicks**, access the clicked URL:

```liquid theme={null}
{% if trigger.type == "email_link_clicked" %}
  You clicked {{ trigger.email.link.url }} in "{{ trigger.email.name }}"
{% endif %}
```

### Field Changes

Access previous values when a contact or company field changes:

```liquid theme={null}
{% if trigger.type == "contact_updated" %}
  {% if trigger.contact._changed.title %}
    Your title changed from "{{ trigger.contact._previous.title }}" to "{{ contact.title }}"
  {% endif %}
{% endif %}
```

```liquid theme={null}
{% if trigger.type == "company_updated" %}
  {% if trigger.company._changed.industry %}
    {{ company.name }}'s industry is now {{ company.industry }}
  {% endif %}
{% endif %}
```

### Audience Changes

```liquid theme={null}
{% if trigger.type == "added_to_audience" %}
  Welcome to {{ trigger.audience.name }}!
{% endif %}
```

### Opportunity Events

```liquid theme={null}
{% if trigger.type == "opportunity_updated" %}
  {% if trigger.opportunity._changed.stage %}
    {{ trigger.opportunity.name }} moved to {{ trigger.opportunity.stage }}
  {% endif %}
{% endif %}
```

### Custom Events

```liquid theme={null}
{% if trigger.type == "custom_event" %}
  Event: {{ trigger.custom_event.name }}
  {{ trigger.custom_event.data.product_id }}
{% endif %}
```

### API Triggers

```liquid theme={null}
{% if trigger.type == "api" %}
  Processing: {{ trigger.api.name }}
  {{ trigger.api.data.campaign_name }}
{% endif %}
```

<Info>
  For a complete list of trigger types and their available fields, see [Trigger Context Reference](/product-docs/liquid/trigger-context-reference).
</Info>

***

## Conditional Logic

Use Liquid tags to add logic to your content:

### If/Else Statements

```liquid theme={null}
{% if contact.industry == "Technology" %}
  Check out our tech solutions.
{% elsif contact.industry == "Healthcare" %}
  See our healthcare offerings.
{% else %}
  Explore our full product suite.
{% endif %}
```

### Case Statements

```liquid theme={null}
{% case trigger.type %}
  {% when "form_submission" %}
    Thanks for filling out {{ trigger.form_submission.form_name }}!
  {% when "email_link_clicked" %}
    Thanks for clicking through from "{{ trigger.email.name }}"
  {% when "opportunity_created" %}
    Excited to kick off {{ trigger.opportunity.name }}!
  {% else %}
    Thanks for your interest!
{% endcase %}
```

### Checking for Empty Values

```liquid theme={null}
{% if contact.company %}
  You work at {{ company.name }}
{% else %}
  Tell us about your company
{% endif %}
```

***

## Filters

Filters transform values. Chain multiple filters with pipes:

```liquid theme={null}
{{ contact.first_name | upcase }}
{{ contact.email | split: "@" | last }}
{{ contact.created_at | date: "%B %d, %Y" }}
```

### Common Filters

<AccordionGroup>
  <Accordion title="upcase">
    Converts text to uppercase.

    ```liquid theme={null}
    {{ "hello" | upcase }}
    ```

    Output: `HELLO`
  </Accordion>

  <Accordion title="downcase">
    Converts text to lowercase.

    ```liquid theme={null}
    {{ "HELLO" | downcase }}
    ```

    Output: `hello`
  </Accordion>

  <Accordion title="capitalize">
    Capitalizes the first character.

    ```liquid theme={null}
    {{ "hello" | capitalize }}
    ```

    Output: `Hello`
  </Accordion>

  <Accordion title="default">
    Provides a fallback value when the input is empty or nil.

    ```liquid theme={null}
    {{ contact.middle_name | default: "N/A" }}
    ```

    Output: `N/A` (if middle\_name is empty)
  </Accordion>

  <Accordion title="date">
    Formats a date using strftime syntax.

    ```liquid theme={null}
    {{ now | date: "%B %d, %Y" }}
    ```

    Output: `January 15, 2025`

    Common format codes:

    * `%Y` — 4-digit year (2025)
    * `%m` — Month as number (01–12)
    * `%B` — Full month name (January)
    * `%d` — Day of month (01–31)
    * `%H` — Hour in 24-hour format (00–23)
    * `%M` — Minute (00–59)
  </Accordion>

  <Accordion title="size">
    Returns the number of items in an array or characters in a string.

    ```liquid theme={null}
    {{ company._opportunities | size }}
    ```

    Output: `3` (if there are 3 opportunities)
  </Accordion>

  <Accordion title="first / last">
    Returns the first or last item of an array.

    ```liquid theme={null}
    {{ company._opportunities | first }}
    {{ company._opportunities | last }}
    ```
  </Accordion>

  <Accordion title="split">
    Divides a string into an array based on a delimiter.

    ```liquid theme={null}
    {{ contact.email | split: "@" | last }}
    ```

    Output: `acme.com` (for email `sarah@acme.com`)
  </Accordion>
</AccordionGroup>

### Fallback Values

Use the `default` filter to provide fallback values when data is missing:

```liquid theme={null}
Hi {{ contact.first_name | default: "there" }},

Your company, {{ company.name | default: "your organization" }}, is in 
the {{ company.industry | default: "your industry" }} space.
```

***

## Handling Missing Data

When Liquid syntax is valid but data isn't available at runtime (for example, a form field wasn't submitted), the expression evaluates to an empty string. This is not treated as an error.

```liquid theme={null}
{{ contact.middle_name }}  {/*  Empty string if not set  */}
{{ trigger.form_submission.fields.optional_field }}  {/*  Empty string if not submitted  */}
```

**Best practices for handling missing data:**

1. **Use fallbacks** for values that might be empty:
   ```liquid theme={null}
   {{ contact.first_name | default: "there" }}
   ```
2. **Use conditionals** to show or hide entire sections:
   ```liquid theme={null}
   {% if company.industry %}
     We specialize in {{ company.industry }}.
   {% endif %}
   ```
3. **Combine both** for maximum flexibility:
   ```liquid theme={null}
   {% if contact.first_name %}
     Hi {{ contact.first_name }},
   {% else %}
     Hi there,
   {% endif %}
   ```

***

## Validation and Errors

Conversion validates Liquid syntax in real-time as you write. Common validation errors include:

| Error Type          | Example                      | Message                                           |
| :------------------ | :--------------------------- | :------------------------------------------------ |
| Invalid field       | `contact.unknown_field`      | "unknown\_field" is not a valid contact field     |
| Invalid object type | `objects.unknown_type`       | "unknown\_type" is not a valid custom object type |
| Invalid index       | `company._opportunities[15]` | Index must be 0–9                                 |
| Invalid token       | `tokens.unknown`             | "unknown" is not a valid token                    |
| Syntax error        | Missing closing braces       | Missing closing braces                            |

### Validation by Context

| Context           | What Happens When Validation Fails                        |
| :---------------- | :-------------------------------------------------------- |
| **Email**         | Email cannot be sent; marked as incomplete in workflows   |
| **Workflow node** | Node marked as "not set up"; workflow cannot be activated |
| **Webhook**       | Webhook cannot be saved                                   |

<Note>
  Validation catches syntax and configuration errors before runtime. Missing data at runtime (like an empty field) is handled gracefully and won't cause errors.
</Note>

***

## Examples

### Personalized Welcome Email

```liquid theme={null}
Hi {{ contact.first_name | default: "there" }},

Welcome to Conversion! 

{% if company.industry %}
We've helped many {{ company.industry }} companies like {{ company.name }} 
streamline their marketing operations.
{% else %}
We're excited to help {{ company.name | default: "your team" }} streamline 
your marketing operations.
{% endif %}

Best,
The Conversion Team
```

### Form Submission Follow-up

```liquid theme={null}
{% if trigger.type == "form_submission" %}
Thanks for your interest in {{ trigger.form_submission.fields.product | default: "our solutions" }}, 
{{ contact.first_name }}!

{% if trigger.form_submission.fields.company_size == "Enterprise" %}
I'd love to schedule a call with our enterprise team to discuss your needs.
{% else %}
Check out our self-serve options to get started right away.
{% endif %}
{% endif %}
```

### Opportunity Stage Change Notification

```liquid theme={null}
{% if trigger.type == "opportunity_updated" %}
{% if trigger.opportunity._changed.stage %}
Great news! {{ trigger.opportunity.name }} just moved from 
{{ trigger.opportunity._previous.stage }} to {{ trigger.opportunity.stage }}.

{% if trigger.opportunity.stage == "Closed Won" %}
🎉 Congratulations on closing the deal!
{% endif %}
{% endif %}
{% endif %}
```

***

## Quick Reference

### Object Syntax

| Object                   | Description                 | Example                             |
| :----------------------- | :-------------------------- | :---------------------------------- |
| `contact`                | Contact fields              | `contact.first_name`                |
| `company`                | Company fields              | `company.name`                      |
| `company._opportunities` | Company's opportunities     | `company._opportunities.first.name` |
| `opportunity_roles`      | Contact's opportunity roles | `opportunity_roles.first.role`      |
| `objects.<type>`         | Custom objects by type      | `objects.product[0].name`           |
| `tokens`                 | Global tokens               | `tokens.promo_code`                 |
| `trigger`                | Trigger context             | `trigger.type`                      |
| `now`                    | Current UTC datetime        | `now` (use with `date` filter)      |

### Naming Conventions

| Pattern                    | Meaning               | Example                               |
| :------------------------- | :-------------------- | :------------------------------------ |
| `object.field`             | Direct field access   | `contact.email`                       |
| `object._relationship`     | Related object        | `company._opportunities`              |
| `object[n]`                | Array index (0–9)     | `opportunity_roles[0]`                |
| `object.first` / `.last`   | First or last item    | `company._opportunities.first`        |
| `trigger.<type>`           | Trigger-specific data | `trigger.form_submission.form_name`   |
| `trigger.object._previous` | Previous value        | `trigger.opportunity._previous.stage` |
| `trigger.object._changed`  | Changed field flags   | `trigger.opportunity._changed.stage`  |

***

## Frequently Asked Questions

<AccordionGroup>
  <Accordion title="Why do some objects start with an underscore?">
    The underscore prefix (`_`) indicates a related object rather than a direct field. For example, `company._opportunities` accesses opportunities related to the company, while `company.name` accesses the company's name field directly.
  </Accordion>

  <Accordion title="What's the difference between company._opportunities and opportunity_roles?">
    `company._opportunities` returns all opportunities associated with the contact's company. `opportunity_roles` returns only opportunities where this specific contact has an Opportunity Contact Role (OCR). A contact might have access to company opportunities they're not directly involved with.
  </Accordion>

  <Accordion title="How many items can I access in arrays?">
    Arrays like `company._opportunities`, `opportunity_roles`, and `objects.<type>` return up to 10 items, accessible via indexes 0–9.
  </Accordion>

  <Accordion title="What happens if I reference a field that doesn't exist?">
    Conversion validates your Liquid in real-time. If you reference a field that doesn't exist (like a typo or deleted field), you'll see a validation error and won't be able to save or send until it's fixed.
  </Accordion>

  <Accordion title="What if a field exists but has no value?">
    If the field exists but is empty for a particular contact, the expression evaluates to an empty string. Use the `default` filter to provide a fallback value.
  </Accordion>

  <Accordion title="Can I use Liquid in email subject lines?">
    Yes! Liquid is supported in both email subject lines and body content. This is a great way to personalize subject lines for better open rates.
  </Accordion>
</AccordionGroup>
