How Liquid Templates Streamline Document Automation Blogpost

Learn how Liquid templating language simplifies dynamic document generation from structured data. Built for developers who need reliable, maintainable document automation.

Why Template-Based Document Generation

Traditional document generation involves complex libraries, format-specific code, and maintenance overhead. Each document type requires different implementation approaches, creating technical debt and development complexity.

What Makes Liquid Templates Effective

Liquid is a safe, designer-friendly templating language originally created by Shopify. It separates data from presentation, making documents maintainable by non-developers while remaining powerful enough for complex logic.

<!-- Simple variable substitution -->
<h1>Invoice {{ invoice.number }}</h1>
<p>Date: {{ invoice.date | date: "%B %d, %Y" }}</p>

<!-- Conditional content -->
{% if invoice.discount > 0 %}
<p>Discount Applied: {{ invoice.discount }}%</p>
{% endif %}

<!-- Loops for dynamic content -->
{% for item in invoice.items %}
<tr>
  <td>{{ item.name }}</td>
  <td>{{ item.quantity }}</td>
  <td>${{ item.price | number }}</td>
</tr>
{% endfor %}

<!-- Built-in filters for formatting -->
<p>Total: ${{ invoice.total | number }}</p>

Template Structure and Data Flow

#Send structured data with template
curl --request POST \
  --url https://dash.liquidtemplater.com/items/template_request \
  --header 'Authorization: Bearer YOUR_BEARER_API_KEY_HERE_1234578' \
  --header 'content-type: application/json' \
  --data '{
    "data": {
      "invoice": {
        "number": "INV-2025-0847",
        "date": "2025-06-20",
        "discount": 10,
        "items": [
          {"name": "Web Development", "quantity": 40, "price": 125},
          {"name": "Design Services", "quantity": 20, "price": 95}
        ],
        "total": 6900
      }
    },
    "template": "<!-- Template content above -->",
    "type": "pdf"
  }'

The same template works across PDF, DOCX, and email formats, eliminating format-specific code.

Advanced Template Features

Filters for Data Transformation

#Date formatting
{{ order.created_at | date: "%B %d, %Y at %I:%M %p" }}

#Number formatting
{{ price | money }}  #$1,234.56
{{ quantity | pluralize: "item", "items" }}  #1 item / 5 items

#String manipulation
{{ customer.name | upcase }}  #JOHN SMITH
{{ description | truncate: 100 }}  #Truncate long text

Complex Logic and Calculations

#Calculate totals within templates
{% assign subtotal = 0 %}
{% for item in order.items %}
  {% assign line_total = item.quantity | times: item.price %}
  {% assign subtotal = subtotal | plus: line_total %}
{% endfor %}

#Conditional sections based on data
{% case customer.tier %}
{% when "premium" %}
  <p>Thank you for being a Premium customer!</p>
{% when "enterprise" %}
  <p>Your dedicated account manager: {{ customer.manager }}</p>
{% else %}
  <p>Upgrade to Premium for additional benefits.</p>
{% endcase %}

Real-World Template Examples

Invoice Template

<!DOCTYPE html>
<html>
<head>
    <style>
        .header { color: #333; border-bottom: 2px solid #ddd; }
        .total { font-weight: bold; font-size: 1.2em; }
    </style>
</head>
<body>
    <div class="header">
        <h1>{{ company.name }}</h1>
        <p>{{ company.address }}</p>
    </div>
    
    <h2>Invoice {{ invoice.number }}</h2>
    
    <table>
        <tr><th>Description</th><th>Qty</th><th>Rate</th><th>Total</th></tr>
        {% for item in invoice.items %}
        <tr>
            <td>{{ item.description }}</td>
            <td>{{ item.quantity }}</td>
            <td>${{ item.rate | number }}</td>
            <td>${{ item.quantity | times: item.rate | number }}</td>
        </tr>
        {% endfor %}
    </table>
    
    <div class="total">
        Total: ${{ invoice.total | number }}
    </div>
</body>
</html>

Report Template

#Monthly Performance Report

##Summary
- Revenue: ${{ report.revenue | number }}
- Growth: {{ report.growth_rate }}%
- Active Users: {{ report.active_users | number }}

##Top Performing Features
{% assign sorted_features = report.features | sort: "usage" | reverse %}
{% for feature in sorted_features limit: 5 %}
{{ forloop.index }}. {{ feature.name }} - {{ feature.usage }}% adoption
{% endfor %}

##Regional Breakdown
{% for region in report.regions %}
###{{ region.name }}
Revenue: ${{ region.revenue | number }}
Users: {{ region.users | number }}
{% endfor %}

Template Reusability

One Template, Multiple Formats

//Generate different formats from same template
async function generateDocument(data, format) {
  const template = `
    <h1>{{ document.title }}</h1>
    <p>{{ document.content }}</p>
    <ul>
    {% for item in document.items %}
      <li>{{ item }}</li>
    {% endfor %}
    </ul>
  `;
  
  const response = await fetch('https://dash.liquidtemplater.com/items/template_request', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${YOUR_BEARER_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      data: data,
      template: template,
      type: format  // 'pdf', 'docx', or 'email'
    })
  });
  
  const { data: { id } } = await response.json();
  const result = await pollForCompletion(id);
  return `https://dash.liquidtemplater.com/assets/${result.data.result}?download=true`;
}

Template Composition

#Include common headers
{% include 'company_header' %}

#Document-specific content
<h2>{{ document.type | capitalize }} #{{ document.number }}</h2>

#Include common footers
{% include 'company_footer' %}

Debugging and Testing Templates

Template Validation

#Use default values for missing data
{{ customer.name | default: "Valued Customer" }}

#Check for data existence
{% if order.shipping_address %}
  <h3>Shipping Address</h3>
  <p>{{ order.shipping_address }}</p>
{% endif %}

#Debug output during development
{{ debug_data | json }}

Common Template Patterns

#Safe number formatting
${{ price | default: 0 | number }}

#Handle empty arrays
{% if items.size > 0 %}
  {% for item in items %}
    <p>{{ item.name }}</p>
  {% endfor %}
{% else %}
  <p>No items found.</p>
{% endif %}

#Nested object access
{{ user.profile.address.city | default: "Not specified" }}

Performance Considerations

Template Efficiency

  • Keep templates focused and avoid deeply nested logic
  • Use filters instead of complex calculations when possible
  • Cache frequently used template components

Data Structure Optimization

  • Pre-calculate totals and summaries in your application
  • Structure data to match template needs
  • Minimize template processing by preparing data properly

Migration from Other Solutions

From String Concatenation

//Old approach
const html = `<h1>${invoice.number}</h1><p>Total: ${invoice.total}</p>`;

//Liquid template approach
const template = `<h1>{{ invoice.number }}</h1><p>Total: ${{ invoice.total | number }}</p>`;

From Complex Template Engines Liquid provides the right balance of power and simplicity, avoiding the complexity of full programming languages in templates while offering more functionality than basic string replacement.

Getting Started with Liquid Templates

  1. Start Simple: Begin with basic variable substitution
  2. Add Logic Gradually: Introduce conditionals and loops as needed
  3. Use Filters: Leverage built-in formatting options
  4. Test Iteratively: Use the interactive demo to refine templates

Template Resources

Built by developers, for developers. No vendor lock-in, no complex setup.

Learn how to get startedInteractive demo