Skip to main content
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:
This outputs: “Hi Sarah, welcome to Acme Corp!”

Where You Can Use Liquid

Liquid is supported throughout Conversion:

Liquid Basics

Liquid consists of three core components:

Objects

Objects output dynamic values using double curly braces:

Tags

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

Filters

Filters modify output using the pipe character:
Conversion supports the core Liquid language — the objects, tags, and filters documented at shopify.github.io/liquid. Filters and tags that only exist on the Shopify platform itself (filters like money, img_url, or t, and tags like render, echo, liquid, increment, and decrement) are not available and will fail validation.

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:

Company

Access fields from the contact’s associated company:

Opportunities

Access opportunities through two paths, depending on the relationship:
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:
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):
Iterate over all opportunities:
Example: reference the most recent opportunity in a Slack messageA common pattern is alerting a Slack channel about the contact’s most recently created opportunity:
Access the contact’s roles on a company opportunity:If the contact has Opportunity Contact Roles (OCRs) on an opportunity, they’re available through the _roles array:
_roles is empty if the contact has no OCRs on that opportunity.

Custom Objects

Records connected to the contact (or its company, or one of its opportunities) are exposed directly on the object they relate to, as relationship fields — addressed by the relationship field’s key, not by the related object type’s name. A relationship on the contact appears under contact., one on the company under company., one on an opportunity under that opportunity, and each related object exposes its own relationships the same way. A to-one relationship resolves to a single record:
A to-many relationship resolves to an array of up to 10 records:
Iterate over a to-many relationship:
Each related object exposes its fields by field key, plus an id field — its unique identifier — and its own relationship keys for the next hop. Each segment is exactly one hop:
A relationship can also land on a contact, a company, or an opportunity, not only on a custom object record. Those resolve the same way, exposing that object’s own fields and id:
A relationship read this way exposes the related object’s fields, not the extras its own namespace adds — company._opportunities and opportunity._roles are reachable through {{ company }} and {{ opportunity }}, not through a hop. A missing, unset, or dangling relationship renders as an empty string rather than erroring.
Relationship fields on a campaign member are the one exception: they render the related record’s id rather than the record, so {{ campaign_member.subscription }} is an id and {{ campaign_member.subscription.plan }} is blank. Read the same record through the contact instead.

Tokens

Access token values set in your campaigns:
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.

Campaign Member

The campaign_member object gives Liquid access to the contact’s campaign membership: the record that links the contact to a specific campaign. Use it to personalize content with member fields (like a webinar join URL or a response date) and to react to the member’s current status.
member is a shorthand alias for campaign_member. The two are interchangeable: {{ member.webinar_url }} renders the same value as {{ campaign_member.webinar_url }}.
Like tokens, campaign member data is resolved from the parent campaign of the asset you are using Liquid in. If the asset is nested more deeply, the nearest ancestor campaign is used. If the asset isn’t inside a campaign, the contact isn’t a member of that campaign, or the referenced field has no value, campaign_member expressions render as empty strings. This is handled gracefully and won’t cause errors. While both tokens and campaign_member resolve against the asset’s parent campaign, they hold different data: tokens are campaign-level values that are the same for every contact in the campaign (like a webinar date), while campaign member fields are per-contact values on the membership record (like a personal join URL or attendance duration). If a contact is a member of multiple campaigns, only the membership in the asset’s parent campaign is referenced. Member fields Access any campaign member field by its field key. Values respect the field’s data type: date and datetime fields work with the date filter, number fields work with math filters, and boolean fields can be used directly in conditionals.
Campaign member fields come from several sources:
  • Custom fields you create in your workspace with the Campaign member object type
  • Workflow writes from the Update Campaign Member Field node
  • Salesforce sync, for campaigns synced to Salesforce campaigns (see Salesforce Sync)
  • Webinar fields, populated automatically by the webinar registration and attendance flows
For webinar campaigns, Conversion maintains a set of read-only, system-populated member fields: Member status The _status object exposes the member’s current status. Values resolve at render time, so the status reflects the member’s current status when the email is sent or the workflow node runs, whether it was changed manually, by the Update Campaign Member node, or by Salesforce sync.

Webinar and Custom Campaign

When the asset you’re writing Liquid in sits inside a campaign, the campaign’s own metadata is available. webinar resolves from the nearest ancestor webinar, and custom_campaign from the nearest ancestor custom campaign. If the asset isn’t inside one, the expressions render as empty strings. Webinar fields: name, title (the provider’s event title, which can differ from name), description, start_time (a real timestamp — use the date filter), duration (minutes), timezone, host (host email), provider (ZOOM, SEQUEL, or GOLDCAST), provider_webinar_id, provider_registration_url, salesforce_id.
Custom campaign fields: name, description, salesforce_id.

Email Variables

The following variables are available only in email body content (they are not replaced in subject lines, preview text, or workflow node content):

Current Date and Time

Use now to get the current UTC datetime:

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

UTM values come from the first page visit in the session that the submission belongs to, not the page the form was submitted on. If someone lands via a campaign link and then browses to a page without UTM parameters before submitting, you still get the original campaign values. If the session has no tracked page visits, the values fall back to the UTM parameters parsed from the URL the form was submitted on.
If a form field name contains spaces (e.g., “Country Of Origin”), you can’t use dot notation. Use bracket syntax instead:

Page Visits

Email Events

All email triggers share common metadata: For link clicks, access the clicked URL:

Field Changes

Access previous values when a contact or company field changes:

Audience Changes

Opportunity Events

Custom Events

API Triggers

For a complete list of trigger types and their available fields, see Trigger Context Reference.

Conditional Logic

Use Liquid tags to add logic to your content:

If/Else Statements

Case Statements

Checking for Empty Values


Filters

Filters transform values. Chain multiple filters with pipes:

Common Filters

Converts text to uppercase.
Output: HELLO
Converts text to lowercase.
Output: hello
Capitalizes the first character.
Output: Hello
Provides a fallback value when the input is empty or nil.
Output: N/A (if middle_name is empty)
Formats a date using strftime syntax.
Output: January 15, 2025Common 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)
Returns the number of items in an array or characters in a string.
Output: 3 (if there are 3 opportunities)
Returns the first or last item of an array.
Divides a string into an array based on a delimiter.
Output: acme.com (for email sarah@acme.com)

Fallback Values

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

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.
Best practices for handling missing data:
  1. Use fallbacks for values that might be empty:
  2. Use conditionals to show or hide entire sections:
  3. Combine both for maximum flexibility:

Validation and Errors

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

Validation by Context

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

Examples

Personalized Welcome Email

Form Submission Follow-up

Status-Aware Webinar Follow-up

Opportunity Stage Change Notification


Quick Reference

Object Syntax

Naming Conventions


Frequently Asked Questions

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.
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.
Arrays like company._opportunities, opportunity_roles, and to-many relationships like contact.subscriptions return up to 10 items, accessible via indexes 0–9.
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.
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.
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.