Liquid is the templating language that powers dynamic document generation in our API. Whether you're creating personalized emails, invoices, or reports, Liquid allows you to merge your structured data with templates to produce professional, customized documents.
Liquid is a safe, customer-facing templating language originally created by Shopify. It's designed to be secure, flexible, and easy to understand, making it perfect for generating dynamic content where you need to combine static templates with variable data from your application.
Think of Liquid as a bridge between your raw data (like user information, order details, or analytics) and the final formatted document your users will see. Instead of manually creating hundreds of individual documents, you write one template that adapts to any data you provide.
Liquid offers several advantages that make it ideal for document generation services. The templates can't execute dangerous operations or access system resources. This security model allows you to safely use templates without worrying about code injection or system compromise.
The syntax is intuitive enough that non-developers can often read and modify templates, yet powerful enough to handle complex document layouts and business logic. Liquid also has excellent support for formatting data, handling missing values gracefully, and organizing content through filters and conditionals.
Liquid accesses data through objects, which represent the structured information you send to our API. When you POST your JSON data along with a template, that data becomes available as Liquid objects.
Hello {{ customer.first_name }},
Your order #{{ order.number }} has been confirmed.
Total: ${{ order.total_price }}
The double curly braces {{ }}
tell Liquid to output the value of whatever is inside. If your data contains a customer object with a first_name property, Liquid will replace {{ customer.first_name }}
with the actual name.
Tags use {% %}
syntax and control the flow and structure of your template. They don't output content directly but determine what content gets shown and how it's organized.
{% if order.status == 'paid' %}
Thank you for your payment!
{% else %}
Payment is still pending.
{% endif %}
Tags let you create conditional sections, loop through arrays of data, assign variables, and organize your template logic. They're the decision-making part of your template.
Filters modify how data appears in your output. They're applied using the pipe symbol |
and can chain together to perform multiple transformations.
{{ order.created_at | date: "%B %d, %Y" }}
{{ customer.email | downcase }}
{{ product.price | money }}
Filters handle common formatting needs like dates, currencies, text case, and mathematical operations. They ensure your data appears exactly how you want it in the final document.
Let's walk through creating a simple email template step by step. Imagine you're sending order confirmation emails and your data looks like this:
{
"customer": {
"first_name": "Sarah",
"email": "sarah@example.com"
},
"order": {
"number": "1001",
"total": 149.99,
"items": [
{"name": "Wireless Headphones", "quantity": 1, "price": 149.99}
]
}
}
Start with basic variable substitution to personalize the greeting:
Dear {{ customer.first_name }},
Thank you for your order #{{ order.number }}.
Add conditional content to handle different scenarios:
{% if order.total > 100 %}
Congratulations! You qualify for free shipping.
{% endif %}
Use loops to display multiple items:
Your order includes:
{% for item in order.items %}
- {{ item.name }} (Quantity: {{ item.quantity }}) - ${{ item.price }}
{% endfor %}
Apply filters to format the data properly:
Order Total: ${{ order.total | round: 2 }}
Many documents need to display lists of information like invoice line items, user permissions, or transaction history. Liquid's for
loops make this straightforward.
{% for product in cart.products %}
<tr>
<td>{{ product.name }}</td>
<td>{{ product.quantity }}</td>
<td>${{ product.price | times: product.quantity }}</td>
</tr>
{% endfor %}
You can access special variables within loops to handle different display requirements. The forloop.first
and forloop.last
variables help you apply special formatting to the beginning or end of lists:
{% for invoice in invoices %}
{% if forloop.first %}
<h3>Recent Invoices</h3>
{% endif %}
Invoice #{{ invoice.number }} - ${{ invoice.amount }}
{% unless forloop.last %}, {% endunless %}
{% endfor %}
The forloop.index
variable gives you the current position in the loop, which is useful for numbering or alternating styles:
{% for step in process_steps %}
{{ forloop.index }}. {{ step.description }}
{% endfor %}
Templates often need to show different content based on data values or user attributes. Liquid provides several ways to implement conditional logic.
Basic if statements handle simple true/false decisions:
{% if user.is_premium %}
Access to premium features included
{% endif %}
Use elsif
for multiple conditions:
{% if order.status == 'pending' %}
Your order is being processed
{% elsif order.status == 'shipped' %}
Your order is on the way
{% elsif order.status == 'delivered' %}
Your order has been delivered
{% else %}
Order status unknown
{% endif %}
The unless
tag provides a cleaner way to express negative conditions:
{% unless user.email_verified %}
Please verify your email address
{% endunless %}
You can combine conditions using and
and or
operators:
{% if user.age >= 18 and user.country == 'US' %}
You're eligible for this promotion
{% endif %}
Real-world data often contains nested objects and arrays. Liquid handles this naturally through dot notation:
{% for order in customer.orders %}
Order #{{ order.number }} from {{ order.created_at | date: "%B %Y" }}
{% for line_item in order.line_items %}
{{ line_item.product.name }} - {{ line_item.quantity }} × ${{ line_item.price }}
{% endfor %}
{% endfor %}
You can also use bracket notation for dynamic property access:
{% assign field_name = 'custom_field_1' %}
{{ product[field_name] }}
One of the most powerful features of our service is that the same Liquid template can generate multiple document formats. This means you can write your template logic once and render it as an email, PDF, or DOCX document without any changes to your Liquid code. This approach saves significant development time and ensures consistency across all your document formats.
The beauty of using Liquid for document generation lies in its format-agnostic nature. Your business logic, data processing, and content structure remain identical regardless of the final output format. Here's how the same template content adapts to different document types:
{%- comment -%}
This template works for email, PDF, and DOCX formats
The same Liquid logic generates all three document types
{%- endcomment -%}
Dear {{ customer.first_name }},
Thank you for your order #{{ order.number }} placed on {{ order.created_at | date: "%B %d, %Y" }}.
{% if order.expedited %}
**Priority Processing:** Your order is being expedited and will ship within 24 hours.
{% endif %}
**Order Summary:**
{% for item in order.items %}
- {{ item.name }} (Qty: {{ item.quantity }}) - ${{ item.price | times: item.quantity }}
{% endfor %}
**Total Amount:** ${{ order.total }}
{% if order.total > 100 %}
Congratulations! You qualify for free shipping on this order.
{% endif %}
Best regards,
{{ company.name }}
This single template can produce a professional email for immediate delivery, a formatted PDF for record-keeping, or a DOCX document for further editing all from the exact same Liquid source code.
While your Liquid logic remains unchanged, our rendering engine automatically applies appropriate formatting for each document type. When generating emails, the service applies HTML formatting and email-safe styling. For PDF generation, it handles typography, spacing, and print-ready layouts. DOCX rendering maintains document structure that works seamlessly with Microsoft Word.
This means you can focus entirely on your content logic and data processing without worrying about the technical differences between document formats. Whether you need to send an immediate email notification, generate a PDF invoice for accounting, or create a DOCX contract for legal review, you write the template once and render it in any format your workflow requires.
This unified approach provides several concrete advantages for development teams. You maintain only one template per document type instead of separate templates for each format. When business requirements change, you update your logic in one place rather than synchronizing changes across multiple format-specific templates. Your data structures and variable names remain consistent across all output formats, reducing complexity in your backend systems.
Consider an invoice workflow where you need to email customers immediately, generate PDFs for your accounting system, and create DOCX files for manual review. With traditional approaches, you would need three separate templates, each with its own maintenance overhead. Our service lets you define the invoice structure once and render it appropriately for each use case.
Always consider what happens when expected data is missing. Use default values and conditional checks to prevent template errors:
{{ customer.first_name | default: "Valued Customer" }}
{% if order.shipping_address %}
Shipping to: {{ order.shipping_address.street }}
{% else %}
Shipping address not provided
{% endif %}
Use whitespace and comments to make complex templates easier to understand and maintain:
{%- comment -%}
Calculate totals section
Handles tax calculation based on customer location
{%- endcomment -%}
{% assign subtotal = 0 %}
{% for item in order.items %}
{% assign subtotal = subtotal | plus: item.total %}
{% endfor %}
{% if customer.location.tax_exempt %}
{% assign tax_amount = 0 %}
{% else %}
{% assign tax_amount = subtotal | times: customer.location.tax_rate %}
{% endif %}
Large datasets can slow template rendering. Use filters to process data efficiently:
{%- comment -%}
Instead of looping through all products to find featured ones,
filter the array first
{%- endcomment -%}
{% assign featured_products = products | where: 'featured', true %}
{% for product in featured_products limit: 5 %}
{{ product.name }}
{% endfor %}
Always test your templates with realistic data that includes edge cases like empty arrays, missing fields, and extreme values. This helps you identify potential issues before they affect your users.
When templates don't produce expected output, start by checking your data structure. You can output debug information directly in your template during development:
{%- comment -%}
Debug: Check what data is available
{%- endcomment -%}
{{ customer | json }}
{%- comment -%}
Debug: Check array size
{%- endcomment -%}
Orders found: {{ orders | size }}
Common issues include incorrect property names, missing data, and filter syntax errors. The JSON filter helps you see exactly what data structure you're working with.
Remember that Liquid is case-sensitive, so customer.firstName
and customer.firstname
are different properties. Also, ensure your JSON data matches the property names you're using in your template.
Once you're comfortable with basic Liquid syntax, experiment with combining different features to create rich, dynamic documents. Start with simple templates and gradually add complexity as you become more familiar with the language.
The key to mastering Liquid is understanding that it's designed to be predictable and safe. Every operation either succeeds with the expected output or fails gracefully with empty content. This reliability makes it perfect for generating professional documents where consistency and accuracy are essential.
Practice with your actual data structures and document requirements. The more you work with Liquid in the context of your specific use cases, the more natural the syntax will become and the more efficiently you'll be able to create powerful, dynamic templates.
For comprehensive reference documentation on Liquid syntax, filters, and advanced features, you can explore the official Liquid documentation maintained by Shopify.
Now that you understand Liquid templating, you can start automating your document generation workflow. Most developers are surprised how quickly they can replace manual document creation with simple API calls that generate professional documents in seconds.
Your free tier includes 100 renders per month - perfect for testing with your actual application data. Check out our complete getting started guide to begin implementing Liquid templates in your application today.