Creating PDF documents with rotativa.io templates

date Nov 19, 2025

Dev editing a PDF template

Are you looking to generate professional PDFs dynamically from your application? Rotativa.io’s template editor makes it easy to create, manage, and preview PDF templates using the powerful Liquid templating language. The Rotativa.io API will then serve your dynamic PDF documents, you’ll just need to send your data as a JSON document. In this guide, we’ll walk you through everything you need to know to get started.

Table of Contents

  1. Getting Started: Choosing Your Template
  2. The Template Editor Interface
  3. Writing Liquid Templates
  4. Working with Sample JSON Data
  5. Live Preview and Testing
  6. Managing Images
  7. Template Validation
  8. Managing Your Templates
  9. Using Templates via API
  10. Tips and Best Practices

Getting Started: Choosing Your Template

When you create a new template, you’ll be presented with several starter options to help you get up and running quickly:

Available Template Starters

  1. Blank - A minimal template with basic HTML structure, perfect for building from scratch
  2. Table - Pre-configured data table layout, ideal for reports and data presentations
  3. Invoice - Professional invoice template with line items, totals, and customer information
  4. Table with Cover Page - Multi-page template combining a cover page with data tables

Each template comes with:

  • Pre-written Liquid markup
  • Sample JSON data that matches the template structure
  • A visual live preview

Create a PDF template

Simply click on any template card to start customizing it. Don’t worry - you can modify everything after selection!


The Template Editor Interface

The template editor is your command center for creating PDF templates. Here’s what you’ll find:

Top Action Bar

  • Back to list - Return to the templates overview
  • View Images - Quick access to your image library
  • Preview PDF - Generate a real PDF preview with your current template
  • Archive/Delete/Re-activate - Template lifecycle management
  • Create/Update Template - Save your work

Editor Tabs

Edit a PDF template

The editor provides three tabs for a complete development experience:

1. Template (Liquid)

This is where the magic happens! Write your HTML with Liquid template syntax. Features include:

  • Syntax highlighting for Liquid tags
  • Real-time validation with error underlining
  • Smart autocomplete
  • Line numbers and code folding
  • Keyboard shortcuts (Ctrl+Shift+I to insert images)

2. Sample JSON Data

Provide test data in JSON format. This data will be used to render your template in the preview tab. Think of it as mock data that represents what your API will send when generating PDFs.

3. Preview

See your template rendered in real-time! The preview updates as you type, showing exactly how your PDF will look. You can even pop out the preview to a separate window for side-by-side editing.


Writing Liquid Templates

Liquid is a powerful templating language that makes it easy to create dynamic content. Here’s a quick primer:

Variable Output

<h1>Hello, {{ customer_name }}!</h1>
<p>Order Number: {{ order.number }}</p>

Conditional Logic

{% if order.total > 1000 %}
  <p class="vip-message">
    Thank you for your large order!
  </p>
{% endif %}

Loops

<table>
  <thead>
    <tr>
      <th>Product</th>
      <th>Quantity</th>
      <th>Price</th>
    </tr>
  </thead>
  <tbody>
    {% for item in order.items %}
    <tr>
      <td>{{ item.name }}</td>
      <td>{{ item.quantity }}</td>
      <td>${{ item.price }}</td>
    </tr>
    {% endfor %}
  </tbody>
</table>

Filters

<p>Ordered on: {{ order.date | date: "%B %d, %Y" }}</p>
<p>Total: {{ order.total | money }}</p>

Security Note

The template editor includes built-in security validation to prevent unsafe code patterns. You’ll see errors if you try to use potentially dangerous constructs.


Working with Sample JSON Data

The Sample JSON Data tab lets you provide test data for your template. This serves two important purposes:

  1. Testing - See how your template looks with real-world data structures
  2. Validation - The editor will warn you if you reference variables that don’t exist in your sample data

Example Sample JSON

{
  "customer_name": "John Smith",
  "company": "Acme Corporation",
  "order": {
    "number": "ORD-12345",
    "date": "2025-01-15",
    "total": 2450.00,
    "items": [
      {
        "name": "Widget A",
        "quantity": 10,
        "price": 120.00
      },
      {
        "name": "Widget B",
        "quantity": 5,
        "price": 250.00
      }
    ]
  }
}

Variable Validation

The editor will display warnings if you use template variables that aren’t defined in your sample JSON. This helps catch typos and missing data fields before you deploy to production.

Note: These are warnings, not errors - you can still save templates with undefined variables if those variables will be provided at runtime.


Live Preview and Testing

The Preview tab shows your template rendered with your sample JSON data in real-time.

Preview Features

  • Live Updates - Changes appear instantly as you type
  • Isolated Rendering - Preview is sandboxed to prevent CSS conflicts
  • Pop-out Window - Click the ↗ button to open preview in a separate window
  • Side-by-Side Editing - With pop-out enabled, edit and preview simultaneously

Preview PDF live

PDF Preview

Click the Preview PDF button in the top action bar to generate an actual PDF file. This shows exactly what your users will receive, including:

  • Proper page breaks
  • PDF-specific styling
  • Print layout
  • Font rendering

Managing Images

Need to include logos, charts, or product photos in your PDFs? The image management system makes it easy.

Accessing Images

Navigate to the Images tab on the Templates & Images page. Here you’ll see:

  • All uploaded images with thumbnails
  • File size and format information
  • Total storage usage
  • Last modified dates

Manage images

Uploading Images

  1. Click Upload Image button
  2. Select an image file (JPG, PNG, GIF, BMP, SVG supported)
  3. Review the file details
  4. Click Upload

Your images are stored in your account’s storage and can be referenced in any template.

Using Images in Templates

There are two ways to insert images:

Method 1: Insert Image Helper

  1. Open the template editor
  2. Press Ctrl+Shift+I or use Command Palette (Ctrl+Shift+P) → “Insert image here”
  3. Select an image from the modal
  4. The HTML img tag is inserted at your cursor position

Method 2: Manual Reference

<img src="/templates//images/logo.png" alt="Company Logo" />

Image Best Practices

  • Optimize images before uploading to reduce PDF file sizes
  • Use descriptive filenames to make images easy to find
  • Include alt text for accessibility
  • Check total storage - keep an eye on your usage limits

Deleting Images

  1. Click the Delete button on any image card
  2. Confirm the deletion
  3. Warning: This action cannot be undone, and any templates referencing the image will break

Template Validation

The template editor includes sophisticated validation to catch errors before you save.

Error Types

Security Errors (Red/Blocking)

These prevent saving and indicate potentially dangerous code:

  • Unsafe JavaScript patterns
  • Script tag injection attempts
  • File system access attempts
  • Other security violations

Syntax Errors (Red/Blocking)

Invalid Liquid syntax:

  • Unclosed tags ({% if %} without {% endif %})
  • Malformed expressions
  • Invalid filter usage

Variable Warnings (Yellow/Non-blocking)

Variables used in template but not in sample JSON:

  • Potential typos
  • Missing data fields
  • Runtime-provided variables (these are okay)

Validation Display

Errors and warnings appear in multiple places:

  1. Inline - Underlined directly in the code editor
  2. Tab Badge - Count of errors/warnings on the Template tab
  3. Alert Boxes - Detailed list below the editor with line/column numbers
  4. Save Button - Disabled with tooltip when errors exist

Managing Your Templates

Template Lifecycle

Templates can exist in two states:

Active Templates

  • Available for PDF generation via API
  • Shown by default in the template list
  • Can be edited, duplicated, or archived

Archived Templates

  • Not available for PDF generation
  • Moved to archived storage
  • Can be re-activated or permanently deleted
  • Useful for templates you might need later

Template Actions

Saving Templates

  1. Edit your template, sample data, and template name
  2. Fix any validation errors
  3. Click Create Template (new) or Update Template (existing)
  4. Success notification appears
  5. Template is immediately available for use

Duplicating Templates

Need to create a variant of an existing template?

  1. Click Duplicate on any template in the list
  2. Template loads in editor with “ Copy” appended to name
  3. Modify as needed
  4. Save as new template

Archiving Templates

To remove a template from active use:

  1. Open the template in the editor
  2. Click Archive button
  3. Confirm the action
  4. Template moves to archived state and archived storage

Re-activating Templates

To bring an archived template back:

  1. Switch filter to “Archived” or “All”
  2. Open the archived template
  3. Click Re-activate
  4. Template returns to active state

Permanent Deletion

Warning: This cannot be undone!

  1. Template must be archived first
  2. Open the archived template
  3. Click Delete button
  4. Confirm the permanent deletion
  5. Template and all files are permanently removed

Filtering Templates

Use the status filter dropdown to view:

  • Active - Only active templates (default)
  • Archived - Only archived templates
  • All - Both active and archived templates

Template UUID

Each template has a unique UUID displayed in the editor. This is your template’s identifier for API calls. Click the 📋 icon to copy it to clipboard.

Grab the UUID to call the API


Using Templates via API

Once you’ve created and saved your templates in the dashboard, you can generate PDFs programmatically from your application using the Rotativa.io API. This section shows you how to integrate PDF generation into your code.

Authentication

All API requests require authentication via the X-ApiKey header.

Getting Your API Key

  1. Visit https://rotativa.io
  2. Sign up for a free account (if you haven’t already)
  3. Navigate to your dashboard to obtain your API key
  4. Keep your API key secure - treat it like a password!

API Endpoint

URL: POST /api/templateRender?templateId={templateId}

Endpoint: The closest to you choosing from:

  • useast.rotativa.io
  • eunorth.rotativa.io
  • ausea.rotativa.io

Headers:

  • X-ApiKey: Your Rotativa.io API key (required)
  • Content-Type: application/json

Query Parameters:

  • templateId: The UUID of your template (copy from the editor, or use the template name)

Request Body:

  • JSON object containing the data to be rendered into the template

Response:

  • Redirects to the generated PDF URL in Azure Blob Storage
  • The PDF is accessible via a SAS (Shared Access Signature) URL
  • Most HTTP clients follow redirects automatically

How It Works

  1. You create and save a template in the dashboard (e.g., “invoice-template”)
  2. Your application makes a POST request to /api/templateRender?templateId=invoice-template
  3. Your request body contains the JSON data to render (e.g., customer info, line items)
  4. Rotativa.io renders your template with the provided data
  5. The API returns a redirect to the generated PDF URL
  6. Your web app can return a HTTP redirect (302 code) to the PDF URl or it can download the PDF to display it, or email it

Flow diagram

Code Examples

Here are complete working examples in popular programming languages:

C# (.NET)

using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

public class RotativaClient
{
    private readonly HttpClient _httpClient;
    private readonly string _apiKey;
    private readonly string _baseUrl;

    public RotativaClient(string apiKey, string baseUrl = "https://useast.rotativa.io")
    {
        _apiKey = apiKey;
        _baseUrl = baseUrl;
        _httpClient = new HttpClient();
        _httpClient.DefaultRequestHeaders.Add("X-ApiKey", _apiKey);
    }

    public async Task<string> GeneratePdfFromTemplateAsync(string templateId, object data)
    {
        var jsonData = JsonSerializer.Serialize(data);
        var content = new StringContent(jsonData, Encoding.UTF8, "application/json");

        var response = await _httpClient.PostAsync(
            $"{_baseUrl}/api/templateRender?templateId={templateId}",
            content
        );

        response.EnsureSuccessStatusCode();

        // The response will be a redirect to the PDF URL
        return response.RequestMessage?.RequestUri?.ToString()
            ?? response.Headers.Location?.ToString()
            ?? throw new Exception("No PDF URL returned");
    }
}

// Usage Example
public class Program
{
    public static async Task Main()
    {
        var client = new RotativaClient("your-api-key-here");

        var invoiceData = new
        {
            invoiceNumber = "INV-2024-001",
            customerName = "John Doe",
            invoiceDate = DateTime.Now.ToString("yyyy-MM-dd"),
            total = 150.00,
            items = new[]
            {
                new { name = "Product A", quantity = 2, price = 50.00 },
                new { name = "Product B", quantity = 1, price = 50.00 }
            }
        };

        try
        {
            string pdfUrl = await client.GeneratePdfFromTemplateAsync("invoice-template", invoiceData);
            Console.WriteLine($"PDF generated: {pdfUrl}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }
    }
}

PHP

<?php

class RotativaClient {
    private $apiKey;
    private $baseUrl;

    public function __construct($apiKey, $baseUrl = 'https://useast.rotativa.io') {
        $this->apiKey = $apiKey;
        $this->baseUrl = $baseUrl;
    }

    public function generatePdfFromTemplate($templateId, $data) {
        $url = $this->baseUrl . '/api/templateRender?templateId=' . urlencode($templateId);

        $ch = curl_init($url);

        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'X-ApiKey: ' . $this->apiKey,
            'Content-Type: application/json'
        ]);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $finalUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);

        curl_close($ch);

        if ($httpCode >= 200 && $httpCode < 300) {
            return $finalUrl;
        } else {
            throw new Exception("Error generating PDF. HTTP Code: $httpCode");
        }
    }
}

// Usage Example
$client = new RotativaClient('your-api-key-here');

$invoiceData = [
    'invoiceNumber' => 'INV-2024-001',
    'customerName' => 'John Doe',
    'invoiceDate' => date('Y-m-d'),
    'total' => 150.00,
    'items' => [
        ['name' => 'Product A', 'quantity' => 2, 'price' => 50.00],
        ['name' => 'Product B', 'quantity' => 1, 'price' => 50.00]
    ]
];

try {
    $pdfUrl = $client->generatePdfFromTemplate('invoice-template', $invoiceData);
    echo "PDF generated: $pdfUrl\n";
} catch (Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
}
?>

Python

import requests
import json
from datetime import datetime

class RotativaClient:
    def __init__(self, api_key, base_url='https://useast.rotativa.io'):
        self.api_key = api_key
        self.base_url = base_url
        self.session = requests.Session()
        self.session.headers.update({'X-ApiKey': api_key})

    def generate_pdf_from_template(self, template_id, data):
        url = f"{self.base_url}/api/templateRender"
        params = {'templateId': template_id}

        response = self.session.post(
            url,
            params=params,
            json=data,
            allow_redirects=True
        )

        response.raise_for_status()

        # Return the final URL after redirects
        return response.url

# Usage Example
if __name__ == '__main__':
    client = RotativaClient('your-api-key-here')

    invoice_data = {
        'invoiceNumber': 'INV-2024-001',
        'customerName': 'John Doe',
        'invoiceDate': datetime.now().strftime('%Y-%m-%d'),
        'total': 150.00,
        'items': [
            {'name': 'Product A', 'quantity': 2, 'price': 50.00},
            {'name': 'Product B', 'quantity': 1, 'price': 50.00}
        ]
    }

    try:
        pdf_url = client.generate_pdf_from_template('invoice-template', invoice_data)
        print(f"PDF generated: {pdf_url}")
    except Exception as e:
        print(f"Error: {str(e)}")

Node.js (JavaScript)

const axios = require('axios');

class RotativaClient {
    constructor(apiKey, baseUrl = 'https://useast.rotativa.io') {
        this.apiKey = apiKey;
        this.baseUrl = baseUrl;
        this.client = axios.create({
            baseURL: baseUrl,
            headers: {
                'X-ApiKey': apiKey,
                'Content-Type': 'application/json'
            },
            maxRedirects: 5
        });
    }

    async generatePdfFromTemplate(templateId, data) {
        try {
            const response = await this.client.post(
                '/api/templateRender',
                data,
                {
                    params: { templateId }
                }
            );

            // Return the final URL after redirects
            return response.request.res.responseUrl || response.config.url;
        } catch (error) {
            throw new Error(`Failed to generate PDF: ${error.message}`);
        }
    }
}

// Usage Example
(async () => {
    const client = new RotativaClient('your-api-key-here');

    const invoiceData = {
        invoiceNumber: 'INV-2024-001',
        customerName: 'John Doe',
        invoiceDate: new Date().toISOString().split('T')[0],
        total: 150.00,
        items: [
            { name: 'Product A', quantity: 2, price: 50.00 },
            { name: 'Product B', quantity: 1, price: 50.00 }
        ]
    };

    try {
        const pdfUrl = await client.generatePdfFromTemplate('invoice-template', invoiceData);
        console.log(`PDF generated: ${pdfUrl}`);
    } catch (error) {
        console.error(`Error: ${error.message}`);
    }
})();

Complete API Workflow Example

Let’s walk through a complete example of creating an invoice PDF:

  1. Create the template in the dashboard:
    • Choose the “Invoice” starter template
    • Customize the layout, add your logo, adjust styling
    • Save it with the name “customer-invoice”
    • Copy the template UUID from the editor
  2. Prepare your data in your application:
    {
      "invoiceNumber": "INV-2024-001",
      "customerName": "Acme Corporation",
      "customerAddress": "123 Main Street, Springfield",
      "invoiceDate": "2024-01-15",
      "dueDate": "2024-02-15",
      "items": [
     {
       "description": "Web Development Services",
       "quantity": 40,
       "rate": 150.00,
       "amount": 6000.00
     },
     {
       "description": "Hosting (Annual)",
       "quantity": 1,
       "rate": 500.00,
       "amount": 500.00
     }
      ],
      "subtotal": 6500.00,
      "tax": 650.00,
      "total": 7150.00
    }
    
  3. Make the API call:
    • POST to /api/templateRender?templateId=customer-invoice
    • Include your API key in the X-ApiKey header
    • Send the JSON data in the request body
  4. Receive the PDF URL:
    • The API redirects to a URL like https://storage.blob.core.windows.net/...?sas_token
    • This URL is valid for a limited time (check your account settings)
    • Download the PDF or provide the link to your user

Error Handling

Proper error handling ensures your application gracefully handles failures.

Common HTTP Status Codes

  • 200 OK - PDF generated successfully (redirect to PDF URL)
  • 400 Bad Request - Missing or invalid templateId, or template not found
  • 401 Unauthorized - Invalid or missing API key
  • 404 Not Found - Template does not exist
  • 500 Internal Server Error - Server error during PDF generation

Error Handling Best Practices

// C# Example
try
{
    string pdfUrl = await client.GeneratePdfFromTemplateAsync(templateId, data);
    // Success - use the PDF
}
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
    // API key is invalid or expired
    logger.LogError("Invalid API key. Please check your credentials.");
}
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
{
    // Template doesn't exist
    logger.LogError($"Template '{templateId}' not found.");
}
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.BadRequest)
{
    // Invalid request or data
    logger.LogError("Invalid request. Check template ID and data format.");
}
catch (Exception ex)
{
    // General error
    logger.LogError($"Failed to generate PDF: {ex.Message}");
}

API Important Notes

  1. Template Identifiers: You can use either the template UUID or the template name as the templateId parameter.

  2. Image References: When your templates include images, they’re automatically served from your account storage at /templates/{accountId}/images/{imageName}. No special handling needed!

  3. Response Format: The API returns a redirect (HTTP 302) to the generated PDF URL. Most HTTP clients follow redirects automatically, giving you the final PDF URL.

  4. PDF URLs: Generated PDF URLs include a SAS (Shared Access Signature) token and are valid for a limited time. Download or cache PDFs if you need long-term access.

  5. Rate Limits: Free accounts have usage limits. Monitor your dashboard for current usage. Upgrade your plan for higher limits.

  6. PDF Generation Engine: PDFs are generated using WeasyPrint, which provides excellent CSS support including:
    • Flexbox and Grid layouts
    • Custom fonts (via @font-face)
    • Page breaks and print media queries
    • CSS transforms and filters
  7. Best Practices:
    • Cache generated PDFs when possible to reduce API calls
    • Use template UUIDs for production (more stable than names)
    • Validate your JSON data before sending to avoid errors
    • Test templates thoroughly with edge cases
    • Monitor API response times and set appropriate timeouts

Tips and Best Practices

Development Workflow

  1. Start with a sample - Use one of the provided starter templates
  2. Build incrementally - Add features one at a time and preview frequently
  3. Use sample JSON - Keep your test data realistic and comprehensive
  4. Test edge cases - What happens with empty arrays? Missing fields?
  5. Preview PDF early - Don’t wait until the end to generate a PDF

Template Organization

  • Use descriptive names - “Customer Invoice Q1 2025” not “Template 3”
  • Version templates - Create new versions instead of overwriting
  • Document complex logic - Add HTML comments to explain tricky sections
  • Archive old versions - Keep your template list clean

Performance Optimization

  • Optimize images - Compress before uploading
  • Minimize external resources - Embed critical CSS
  • Test with realistic data - Use representative data volumes
  • Avoid deep nesting - Keep your Liquid logic simple

Security Considerations

  • Trust the validator - If it flags security issues, take them seriously
  • Sanitize user input - Be careful with user-provided data in templates
  • Test with malicious data - What if someone sends HTML in a field?

Unsaved Changes Protection

The editor protects you from accidental data loss:

  • Yellow border appears when you have unsaved changes
  • Browser warns before closing/refreshing
  • Navigation is blocked until you save or discard changes

Keyboard Shortcuts

  • Ctrl+S - Save template (browser default may override)
  • Ctrl+Shift+I - Insert image
  • Ctrl+Shift+P - Open command palette
  • Ctrl+F - Find in template
  • Ctrl+H - Find and replace

Conclusion

The Rotativa.io template editor provides everything you need to create professional, dynamic PDF templates. With features like live preview, validation, image management, and a powerful Liquid templating engine, you can build complex PDF documents with confidence. And with the simple REST API, you can integrate PDF generation into any application with just a few lines of code.

Quick Reference

Template Creation:

  • Choose starter template → Customize in editor → Save
  • Sample JSON provides test data and validates variable usage
  • Live preview shows real-time rendering
  • Upload images then insert with Ctrl+Shift+I
  • Validation catches errors before you save
  • Archive old templates instead of deleting
  • Copy template UUID for API integration

API Integration:

  • Authenticate with X-ApiKey header
  • POST to /api/templateRender?templateId={uuid}
  • Send JSON data in request body
  • Receive PDF URL automatically
  • Handle errors gracefully with proper status code checks

The Complete Workflow

  1. Design - Create templates in the visual editor with live preview
  2. Test - Validate with sample JSON data
  3. Save - Store templates in your account
  4. Integrate - Call the API from your application code
  5. Generate - Receive production-ready PDFs instantly
  6. Deliver - Download, display, or email PDFs to your users

Next Steps

Getting Started:

  1. Log in to your Rotativa.io dashboard
  2. Navigate to Templates & Images
  3. Click “Add Template”
  4. Choose a starter template
  5. Start customizing!

Going Live:

  1. Copy your template UUID from the editor
  2. Get your API key from the dashboard
  3. Integrate the API client code (see examples above)
  4. Test with real data
  5. Deploy to production!

Additional Resources

  • Liquid Templating Syntax - Complete Liquid language reference
  • Your Rotativa.io Dashboard - Monitor usage, manage templates, get support
  • API Documentation - Additional endpoints and advanced features

Happy templating! If you run into any issues or have questions, check out our documentation or contact support at https://rotativa.io.