Template Syntax
Docstron uses the powerful Jinja2 templating engine to process your HTML templates. This guide covers the most common syntax patterns you’ll need to create dynamic PDF documents.
Basic Variable Substitution
Section titled “Basic Variable Substitution”Simple Variables
Section titled “Simple Variables”Use double curly braces {{ }} to insert variables into your template:
<h1>Hello, {{ customer_name }}!</h1><p>Invoice #{{ invoice_number }}</p><p>Total Amount: {{ total_amount }}</p>Data to pass:
{ "customer_name": "John Doe", "invoice_number": "INV-12345", "total_amount": "$299.00"}Output:
<h1>Hello, John Doe!</h1><p>Invoice #INV-12345</p><p>Total Amount: $299.00</p>Accessing Nested Objects
Section titled “Accessing Nested Objects”Access nested object properties using dot notation:
<h2>Customer Information</h2><p>Name: {{ customer.name }}</p><p>Email: {{ customer.email }}</p><p>Address: {{ customer.address.street }}, {{ customer.address.city }}</p>Data to pass:
{ "customer": { "name": "John Doe", "email": "john@example.com", "address": { "street": "123 Main St", "city": "New York" } }}Conditional Statements
Section titled “Conditional Statements”If Statements
Section titled “If Statements”Show or hide content based on conditions:
{% if is_paid %}<div class="status-paid">PAID</div>{% else %}<div class="status-unpaid">UNPAID</div>{% endif %}Data to pass:
{ "is_paid": true}If with Multiple Conditions
Section titled “If with Multiple Conditions”{% if total_amount > 1000 %}<p class="high-value">High Value Order</p>{% elif total_amount > 500 %}<p class="medium-value">Medium Value Order</p>{% else %}<p class="low-value">Standard Order</p>{% endif %}Checking if Variable Exists
Section titled “Checking if Variable Exists”{% if discount %}<p>Discount Applied: {{ discount }}%</p>{% endif %} {% if not shipping_address %}<p>No shipping address provided</p>{% endif %}Multiple Conditions
Section titled “Multiple Conditions”{% if is_premium and total_amount > 500 %}<p>Premium customer discount applied!</p>{% endif %} {% if status == 'shipped' or status == 'delivered' %}<p>Order is on the way!</p>{% endif %}Iterating Over Lists
Section titled “Iterating Over Lists”Display items from an array:
<table> <thead> <tr> <th>Item</th> <th>Quantity</th> <th>Price</th> </tr> </thead> <tbody> {% for item in items %} <tr> <td>{{ item.name }}</td> <td>{{ item.quantity }}</td> <td>${{ item.price }}</td> </tr> {% endfor %} </tbody></table>Data to pass:
{ "items": [ { "name": "Product A", "quantity": 2, "price": "29.99" }, { "name": "Product B", "quantity": 1, "price": "49.99" }, { "name": "Product C", "quantity": 3, "price": "19.99" } ]}Loop with Index
Section titled “Loop with Index”Access the loop index and count:
<ul> {% for product in products %} <li>{{ loop.index }}. {{ product.name }} - ${{ product.price }}</li> {% endfor %}</ul>Available loop variables:
loop.index- Current iteration (starts at 1)loop.index0- Current iteration (starts at 0)loop.first- True if first iterationloop.last- True if last iterationloop.length- Total number of items
Conditional Styling in Loops
Section titled “Conditional Styling in Loops”<table> {% for item in items %} <tr {% if loop.index is odd %}class="odd-row" {% else %}class="even-row" {% endif %} > <td>{{ item.name }}</td> <td>{{ item.price }}</td> </tr> {% endfor %}</table>Loop with Else (Empty Lists)
Section titled “Loop with Else (Empty Lists)”<ul> {% for item in cart_items %} <li>{{ item.name }} - ${{ item.price }}</li> {% else %} <li>Your cart is empty</li> {% endfor %}</ul>Filters
Section titled “Filters”Filters modify variables before display using the pipe | symbol.
Common Text Filters
Section titled “Common Text Filters”<!-- Uppercase --><h1>{{ customer_name | upper }}</h1><!-- Output: JOHN DOE -->
<!-- Lowercase --><p>{{ email | lower }}</p><!-- Output: john@example.com -->
<!-- Capitalize first letter --><p>{{ status | capitalize }}</p><!-- Output: Pending -->
<!-- Title case --><h2>{{ product_name | title }}</h2><!-- Output: Premium Wireless Headphones -->Number Formatting
Section titled “Number Formatting”<!-- Round to 2 decimal places --><p>Price: ${{ price | round(2) }}</p>
<!-- Format as integer --><p>Quantity: {{ quantity | int }}</p>
<!-- Absolute value --><p>Amount: {{ balance | abs }}</p>Default Values
Section titled “Default Values”Provide fallback values for missing data:
<p>Phone: {{ phone_number | default('Not provided') }}</p><p>Notes: {{ customer_notes | default('No notes') }}</p>String Length
Section titled “String Length”<p>Description: {{ description | truncate(100) }}</p><!-- Truncates to 100 characters -->
<p>Characters: {{ message | length }}</p><!-- Shows length of string -->Date Formatting
Section titled “Date Formatting”<p>Date: {{ order_date | date }}</p><p>Created: {{ created_at | datetime }}</p>Combining Filters
Section titled “Combining Filters”Chain multiple filters together:
<h2>{{ product_name | title | truncate(50) }}</h2><p>{{ description | lower | default('No description available') }}</p>Mathematical Operations
Section titled “Mathematical Operations”Perform calculations within templates:
<!-- Basic arithmetic --><p>Subtotal: ${{ price * quantity }}</p><p>Tax (10%): ${{ (price * quantity) * 0.10 }}</p><p>Total: ${{ (price * quantity) * 1.10 }}</p>
<!-- With variables --><p>Total: ${{ subtotal + tax + shipping }}</p><p>Discount: -${{ total * (discount_percentage / 100) }}</p>Calculate in Loops
Section titled “Calculate in Loops”{% set total = 0 %}<table> {% for item in items %} {% set item_total = item.price * item.quantity %} <tr> <td>{{ item.name }}</td> <td>{{ item.quantity }}</td> <td>${{ item.price }}</td> <td>${{ item_total }}</td> </tr> {% set total = total + item_total %} {% endfor %} <tr class="total-row"> <td colspan="3"><strong>Total:</strong></td> <td><strong>${{ total }}</strong></td> </tr></table>Variables and Assignments
Section titled “Variables and Assignments”Set variables within the template:
{% set company_name = "Acme Corporation" %} {% set current_year = 2025 %}
<footer> <p>© {{ current_year }} {{ company_name }}</p></footer>Calculated Variables
Section titled “Calculated Variables”{% set subtotal = price * quantity %} {% set tax = subtotal * 0.08 %} {% settotal = subtotal + tax %}
<div class="summary"> <p>Subtotal: ${{ subtotal }}</p> <p>Tax (8%): ${{ tax }}</p> <p>Total: ${{ total }}</p></div>Comments
Section titled “Comments”Add comments that won’t appear in the output:
{# This is a comment - it won't be rendered #}
<h1>Invoice</h1>{# TODO: Add customer logo here #} {# Multi-line comment for longer notes #}Whitespace Control
Section titled “Whitespace Control”Control whitespace and line breaks:
<!-- Remove whitespace before --><p>Hello {{- customer_name }}</p>
<!-- Remove whitespace after --><p>{{ greeting -}} World</p>
<!-- Remove whitespace on both sides --><p>{{- value -}}</p>Escaping HTML
Section titled “Escaping HTML”By default, Jinja2 escapes HTML. To render raw HTML:
<!-- Escaped (safe) --><div>{{ user_input }}</div>
<!-- Raw HTML (use with caution) --><div>{{ html_content | safe }}</div>⚠️ Warning: Only use | safe with trusted content to prevent security issues.
Complete Examples
Section titled “Complete Examples”Invoice Template
Section titled “Invoice Template”<!DOCTYPE html><html> <head> <style> body { font-family: Arial, sans-serif; } .header { text-align: center; margin-bottom: 30px; } .info-section { margin: 20px 0; } table { width: 100%; border-collapse: collapse; } th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } th { background-color: #4caf50; color: white; } .total { font-size: 1.2em; font-weight: bold; } .paid { color: green; } .unpaid { color: red; } </style> </head> <body> <div class="header"> <h1>INVOICE</h1> <p>Invoice #{{ invoice_number }}</p> <p>Date: {{ invoice_date }}</p> </div>
<div class="info-section"> <h3>Bill To:</h3> <p><strong>{{ customer.name }}</strong></p> <p>{{ customer.email }}</p> {% if customer.phone %} <p>Phone: {{ customer.phone }}</p> {% endif %} <p>{{ customer.address.street }}</p> <p> {{ customer.address.city }}, {{ customer.address.state }} {{ customer.address.zip }} </p> </div>
<table> <thead> <tr> <th>#</th> <th>Description</th> <th>Quantity</th> <th>Unit Price</th> <th>Total</th> </tr> </thead> <tbody> {% set grand_total = 0 %} {% for item in items %} {% set item_total = item.quantity * item.unit_price %} <tr> <td>{{ loop.index }}</td> <td>{{ item.description }}</td> <td>{{ item.quantity }}</td> <td>${{ item.unit_price }}</td> <td>${{ item_total }}</td> </tr> {% set grand_total = grand_total + item_total %} {% endfor %} </tbody> </table>
<div class="info-section"> {% if discount > 0 %} <p>Subtotal: ${{ grand_total }}</p> <p>Discount ({{ discount }}%): -${{ grand_total * (discount / 100) }}</p> {% set final_total = grand_total - (grand_total * (discount / 100)) %} {% else %} {% set final_total = grand_total %} {% endif %}
<p class="total">Total: ${{ final_total }}</p>
<p class="{% if is_paid %}paid{% else %}unpaid{% endif %}"> Status: {% if is_paid %}PAID{% else %}UNPAID{% endif %} </p> </div>
{% if notes %} <div class="info-section"> <h3>Notes:</h3> <p>{{ notes }}</p> </div> {% endif %}
<div class="info-section"> <p>Thank you for your business!</p> </div> </body></html>Data to pass:
{ "invoice_number": "INV-2025-001", "invoice_date": "2025-10-18", "customer": { "name": "John Doe", "email": "john@example.com", "phone": "+1-555-0123", "address": { "street": "123 Main Street", "city": "New York", "state": "NY", "zip": "10001" } }, "items": [ { "description": "Web Design Service", "quantity": 1, "unit_price": 1500 }, { "description": "SEO Optimization", "quantity": 1, "unit_price": 800 }, { "description": "Monthly Maintenance", "quantity": 3, "unit_price": 200 } ], "discount": 10, "is_paid": false, "notes": "Payment due within 30 days"}Certificate Template
Section titled “Certificate Template”<!DOCTYPE html><html> <head> <style> body { font-family: "Georgia", serif; text-align: center; padding: 50px; } .certificate { border: 10px solid #4caf50; padding: 40px; } h1 { font-size: 48px; color: #4caf50; } .recipient { font-size: 36px; font-weight: bold; margin: 30px 0; } .course { font-size: 24px; font-style: italic; } </style> </head> <body> <div class="certificate"> <h1>Certificate of Completion</h1>
<p>This is to certify that</p>
<p class="recipient">{{ student_name | upper }}</p>
<p>has successfully completed</p>
<p class="course">{{ course_name }}</p>
{% if grade %} <p>with a grade of <strong>{{ grade }}</strong></p> {% endif %}
<p>on {{ completion_date }}</p>
{% if instructor %} <p style="margin-top: 50px;"> _________________________<br /> {{ instructor.name }}<br /> {{ instructor.title }} </p> {% endif %} </div> </body></html>Best Practices
Section titled “Best Practices”✅ Do’s
Section titled “✅ Do’s”- Use descriptive variable names -
customer_nameinstead ofcn - Provide default values - Use
{{ variable | default('N/A') }} - Handle empty lists - Use
{% for %}...{% else %}pattern - Keep logic simple - Complex calculations should be done before passing data
- Test with sample data - Always test templates with realistic data
- Comment complex sections - Use
{# comments #}for clarity
❌ Don’ts
Section titled “❌ Don’ts”- Don’t use complex business logic - Keep templates focused on presentation
- Don’t forget to handle missing data - Always provide defaults or conditionals
- Don’t use
| safeon user input - Risk of XSS vulnerabilities - Don’t nest loops too deeply - Can impact PDF generation performance
- Don’t hardcode values - Use variables for all dynamic content
Testing Your Templates
Section titled “Testing Your Templates”Test your templates with sample data before generating PDFs:
- Start simple - Test with basic variable substitution first
- Add complexity gradually - Add loops, conditionals, and filters one at a time
- Test edge cases - Empty lists, missing data, special characters
- Verify calculations - Double-check mathematical operations
- Check formatting - Ensure filters produce expected output
Common Pitfalls
Section titled “Common Pitfalls”Missing Variables
Section titled “Missing Variables”<!-- Bad: Will error if customer_name doesn't exist --><h1>Hello {{ customer_name }}</h1>
<!-- Good: Provides a default --><h1>Hello {{ customer_name | default('Customer') }}</h1>Incorrect Loop Variables
Section titled “Incorrect Loop Variables”<!-- Bad: Using wrong variable name -->{% for product in products %}<p>{{ item.name }}</p>{# Should be product.name #} {% endfor %}
<!-- Good: Consistent naming -->{% for product in products %}<p>{{ product.name }}</p>{% endfor %}Forgetting to Close Tags
Section titled “Forgetting to Close Tags”<!-- Bad: Missing endif -->{% if condition %}<p>Content</p>
<!-- Good: Properly closed -->{% if condition %}<p>Content</p>{% endif %}Need Help?
Section titled “Need Help?”If you need assistance with template syntax:
- 📚 Jinja2 Documentation: jinja.palletsprojects.com
- 📧 Email Support: support@docstron.com
- 💬 Live Chat: Available in your dashboard
- 🎯 Examples: Check our template examples repository