Skip to footer content
MIGRATION GUIDES

Migrating from Fluid (Templating) to IronPDF

Fluid is a .NET library that implements the Liquid templating language, offering developers a flexible way to render dynamic templates and separate content from presentation logic. While Fluid is effective at generating dynamic text outputs, it does not directly support PDF generation—developers must integrate an additional PDF library to convert the HTML output to PDF documents. This two-library approach introduces complexity that many development teams seek to eliminate.

This guide provides a complete migration path from Fluid (templating) with external PDF libraries to IronPDF, with step-by-step instructions, code comparisons, and practical examples for professional .NET developers evaluating this transition.

Why Migrate from Fluid (Templating) to IronPDF

Fluid is a solid Liquid-based templating engine, but using it for PDF generation introduces significant complexity:

Two-Library Dependency: Fluid only generates HTML—you need a separate PDF library (wkhtmltopdf, PuppeteerSharp, etc.) to create PDFs, doubling your dependencies and maintenance burden.

Integration Complexity: Coordinating two libraries means managing two sets of configurations, error handling, and updates. When something breaks, debugging becomes more challenging.

Liquid Syntax Learning Curve: Developers must learn Liquid templating syntax ({{ }}, {% %}) when C# already has powerful string handling capabilities built-in.

Limited PDF Control: Your PDF output quality depends on whichever PDF library you choose to pair with Fluid, not on a dedicated rendering engine.

Debugging Challenges: Errors can occur at either the templating or PDF generation stage, making troubleshooting harder than with a single integrated solution.

Thread Safety Concerns: TemplateContext is not thread-safe and requires careful management in concurrent applications.

IronPDF vs Fluid (Templating): Feature Comparison

Understanding the architectural differences helps technical decision-makers evaluate the migration investment:

AspectFluid + PDF LibraryIronPDF
Dependencies2+ packages (Fluid + PDF library)Single package
TemplatingLiquid syntax ({{ }})C# string interpolation or Razor
PDF GenerationExternal library requiredBuilt-in Chromium engine
CSS SupportDepends on PDF libraryFull CSS3 with Flexbox/Grid
JavaScriptDepends on PDF libraryFull JavaScript support
Thread SafetyTemplateContext not thread-safeChromePdfRenderer is thread-safe
Learning CurveLiquid + PDF library APIHTML/CSS (web standards)
Error HandlingTwo error sourcesSingle error source

Quick Start: Fluid to IronPDF Migration

The migration can begin immediately with these foundational steps.

Step 1: Replace NuGet Packages

Remove Fluid and any external PDF library:

# Remove Fluid and external PDF library
dotnet remove package Fluid.Core
dotnet remove package WkHtmlToPdf-DotNet  # or whatever PDF library you used
dotnet remove package PuppeteerSharp       # if used
# Remove Fluid and external PDF library
dotnet remove package Fluid.Core
dotnet remove package WkHtmlToPdf-DotNet  # or whatever PDF library you used
dotnet remove package PuppeteerSharp       # if used
SHELL

Install IronPDF:

# Install IronPDF (all-in-one solution)
dotnet add package IronPdf
# Install IronPDF (all-in-one solution)
dotnet add package IronPdf
SHELL

Step 2: Update Namespaces

Replace Fluid namespaces with IronPdf:

// Before (Fluid + external PDF library)
using Fluid;
using Fluid.Values;
using SomeExternalPdfLibrary;

// After (IronPDF)
using IronPdf;
using IronPdf.Rendering;  // For RenderingOptions
// Before (Fluid + external PDF library)
using Fluid;
using Fluid.Values;
using SomeExternalPdfLibrary;

// After (IronPDF)
using IronPdf;
using IronPdf.Rendering;  // For RenderingOptions
$vbLabelText   $csharpLabel

Step 3: Initialize License

Add license initialization at application startup:

IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
$vbLabelText   $csharpLabel

Code Migration Examples

Basic HTML to PDF

The most fundamental operation reveals the key difference between these approaches.

Fluid Approach:

// NuGet: Install-Package Fluid.Core
using Fluid;
using System.IO;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        var parser = new FluidParser();
        var template = parser.Parse("<html><body><h1>Hello {{name}}!</h1></body></html>");
        var context = new TemplateContext();
        context.SetValue("name", "World");
        var html = await template.RenderAsync(context);

        // Fluid only generates HTML - you'd need another library to convert to PDF
        File.WriteAllText("output.html", html);
    }
}
// NuGet: Install-Package Fluid.Core
using Fluid;
using System.IO;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        var parser = new FluidParser();
        var template = parser.Parse("<html><body><h1>Hello {{name}}!</h1></body></html>");
        var context = new TemplateContext();
        context.SetValue("name", "World");
        var html = await template.RenderAsync(context);

        // Fluid only generates HTML - you'd need another library to convert to PDF
        File.WriteAllText("output.html", html);
    }
}
$vbLabelText   $csharpLabel

IronPDF Approach:

// NuGet: Install-Package IronPdf
using IronPdf;
using System;

class Program
{
    static void Main()
    {
        var renderer = new ChromePdfRenderer();
        var html = "<html><body><h1>Hello World!</h1></body></html>";
        var pdf = renderer.RenderHtmlAsPdf(html);
        pdf.SaveAs("output.pdf");
    }
}
// NuGet: Install-Package IronPdf
using IronPdf;
using System;

class Program
{
    static void Main()
    {
        var renderer = new ChromePdfRenderer();
        var html = "<html><body><h1>Hello World!</h1></body></html>";
        var pdf = renderer.RenderHtmlAsPdf(html);
        pdf.SaveAs("output.pdf");
    }
}
$vbLabelText   $csharpLabel

Fluid requires creating a FluidParser, parsing the template string, creating a TemplateContext, calling SetValue() for each variable, rendering asynchronously to get HTML, and then writing to a file—which still isn't a PDF. The comment in the code explicitly states "Fluid only generates HTML - you'd need another library to convert to PDF."

IronPDF eliminates this complexity: create a renderer, call RenderHtmlAsPdf(), and save directly to PDF. No intermediate HTML files, no additional libraries.

For advanced HTML-to-PDF scenarios, see the HTML to PDF conversion guide.

Invoice Template with Dynamic Data

Document templates with multiple variables show the templating pattern differences clearly.

Fluid Approach:

// NuGet: Install-Package Fluid.Core
using Fluid;
using System;
using System.IO;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        var parser = new FluidParser();
        var template = parser.Parse(@"
            <html><body>
                <h1>Invoice #{{invoiceNumber}}</h1>
                <p>Date: {{date}}</p>
                <p>Customer: {{customer}}</p>
                <p>Total: ${{total}}</p>
            </body></html>");

        var context = new TemplateContext();
        context.SetValue("invoiceNumber", "12345");
        context.SetValue("date", DateTime.Now.ToShortDateString());
        context.SetValue("customer", "John Doe");
        context.SetValue("total", 599.99);

        var html = await template.RenderAsync(context);
        // Fluid outputs HTML - requires additional PDF library
        File.WriteAllText("invoice.html", html);
    }
}
// NuGet: Install-Package Fluid.Core
using Fluid;
using System;
using System.IO;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        var parser = new FluidParser();
        var template = parser.Parse(@"
            <html><body>
                <h1>Invoice #{{invoiceNumber}}</h1>
                <p>Date: {{date}}</p>
                <p>Customer: {{customer}}</p>
                <p>Total: ${{total}}</p>
            </body></html>");

        var context = new TemplateContext();
        context.SetValue("invoiceNumber", "12345");
        context.SetValue("date", DateTime.Now.ToShortDateString());
        context.SetValue("customer", "John Doe");
        context.SetValue("total", 599.99);

        var html = await template.RenderAsync(context);
        // Fluid outputs HTML - requires additional PDF library
        File.WriteAllText("invoice.html", html);
    }
}
$vbLabelText   $csharpLabel

IronPDF Approach:

// NuGet: Install-Package IronPdf
using IronPdf;
using System;

class Program
{
    static void Main()
    {
        var renderer = new ChromePdfRenderer();
        var invoiceNumber = "12345";
        var date = DateTime.Now.ToShortDateString();
        var customer = "John Doe";
        var total = 599.99;

        var html = $@"
            <html><body>
                <h1>Invoice #{invoiceNumber}</h1>
                <p>Date: {date}</p>
                <p>Customer: {customer}</p>
                <p>Total: ${total}</p>
            </body></html>";

        var pdf = renderer.RenderHtmlAsPdf(html);
        pdf.SaveAs("invoice.pdf");
    }
}
// NuGet: Install-Package IronPdf
using IronPdf;
using System;

class Program
{
    static void Main()
    {
        var renderer = new ChromePdfRenderer();
        var invoiceNumber = "12345";
        var date = DateTime.Now.ToShortDateString();
        var customer = "John Doe";
        var total = 599.99;

        var html = $@"
            <html><body>
                <h1>Invoice #{invoiceNumber}</h1>
                <p>Date: {date}</p>
                <p>Customer: {customer}</p>
                <p>Total: ${total}</p>
            </body></html>";

        var pdf = renderer.RenderHtmlAsPdf(html);
        pdf.SaveAs("invoice.pdf");
    }
}
$vbLabelText   $csharpLabel

Fluid uses Liquid's {{variable}} syntax with context.SetValue() for each variable. The comment explicitly notes "Fluid outputs HTML - requires additional PDF library." IronPDF uses standard C# string interpolation ($"{variable}")—syntax developers already know—and outputs directly to PDF.

Explore the IronPDF tutorials for more document generation patterns.

Dynamic Data with Loops

Templates with collections and loops demonstrate control flow differences.

Fluid Approach:

// NuGet: Install-Package Fluid.Core
using Fluid;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        var parser = new FluidParser();
        var template = parser.Parse(@"
            <html><body>
                <h1>{{title}}</h1>
                <ul>
                {% for item in items %}
                    <li>{{item}}</li>
                {% endfor %}
                </ul>
            </body></html>");

        var context = new TemplateContext();
        context.SetValue("title", "My List");
        context.SetValue("items", new[] { "Item 1", "Item 2", "Item 3" });

        var html = await template.RenderAsync(context);
        // Fluid generates HTML only - separate PDF conversion needed
        File.WriteAllText("template-output.html", html);
    }
}
// NuGet: Install-Package Fluid.Core
using Fluid;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        var parser = new FluidParser();
        var template = parser.Parse(@"
            <html><body>
                <h1>{{title}}</h1>
                <ul>
                {% for item in items %}
                    <li>{{item}}</li>
                {% endfor %}
                </ul>
            </body></html>");

        var context = new TemplateContext();
        context.SetValue("title", "My List");
        context.SetValue("items", new[] { "Item 1", "Item 2", "Item 3" });

        var html = await template.RenderAsync(context);
        // Fluid generates HTML only - separate PDF conversion needed
        File.WriteAllText("template-output.html", html);
    }
}
$vbLabelText   $csharpLabel

IronPDF Approach:

// NuGet: Install-Package IronPdf
using IronPdf;
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        var renderer = new ChromePdfRenderer();
        var title = "My List";
        var items = new[] { "Item 1", "Item 2", "Item 3" };

        var html = $@"
            <html><body>
                <h1>{title}</h1>
                <ul>";

        foreach (var item in items)
        {
            html += $"<li>{item}</li>";
        }

        html += "</ul></body></html>";

        var pdf = renderer.RenderHtmlAsPdf(html);
        pdf.SaveAs("template-output.pdf");
    }
}
// NuGet: Install-Package IronPdf
using IronPdf;
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        var renderer = new ChromePdfRenderer();
        var title = "My List";
        var items = new[] { "Item 1", "Item 2", "Item 3" };

        var html = $@"
            <html><body>
                <h1>{title}</h1>
                <ul>";

        foreach (var item in items)
        {
            html += $"<li>{item}</li>";
        }

        html += "</ul></body></html>";

        var pdf = renderer.RenderHtmlAsPdf(html);
        pdf.SaveAs("template-output.pdf");
    }
}
$vbLabelText   $csharpLabel

Fluid uses Liquid's {% for item in items %}...{% endfor %} syntax—a templating language developers must learn. The comment notes "Fluid generates HTML only - separate PDF conversion needed." IronPDF uses standard C# foreach loops—no new syntax to learn—and outputs directly to PDF.

Fluid API to IronPDF Mapping Reference

This mapping accelerates migration by showing direct API equivalents:

Core Class Mapping

Fluid ClassIronPDF EquivalentNotes
FluidParserN/ANot needed—use C# strings
FluidTemplateN/ANot needed
TemplateContextC# objects/stringsPass data directly
TemplateOptionsRenderingOptionsPDF output configuration
FluidValueNative C# typesNo conversion needed
External PDF classChromePdfRendererMain rendering class

Method Mapping

Fluid MethodIronPDF EquivalentNotes
new FluidParser()new ChromePdfRenderer()Create renderer instead
parser.Parse(source)N/ANot needed—HTML is a string
template.RenderAsync(context)renderer.RenderHtmlAsPdf(html)Direct PDF rendering
context.SetValue("key", value)var key = value;Use C# variables

Liquid Syntax to C# Mapping

Liquid SyntaxC# EquivalentNotes
{{ variable }}$"{variable}"String interpolation
{% for item in items %}foreach (var item in items)C# loop
{% if condition %}if (condition)C# conditional
{{ x \| upcase }}x.ToUpper()C# method
{{ x \| date: '%Y-%m-%d' }}x.ToString("yyyy-MM-dd")C# formatting
{{ x \| number_with_precision: 2 }}x.ToString("F2")C# number formatting

Common Migration Issues and Solutions

Issue 1: Liquid Syntax Conversion

Fluid: Uses {{ variable }} and {% control %} syntax.

Solution: Replace with C# string interpolation and control flow:

// Liquid: {{ name | upcase }}
// C#: $"{name.ToUpper()}"

// Liquid: {% for item in items %}{{item}}{% endfor %}
// C#: foreach (var item in items) { html += $"{item}"; }
// Liquid: {{ name | upcase }}
// C#: $"{name.ToUpper()}"

// Liquid: {% for item in items %}{{item}}{% endfor %}
// C#: foreach (var item in items) { html += $"{item}"; }
$vbLabelText   $csharpLabel

Issue 2: TemplateContext Variables

Fluid: Uses context.SetValue("key", value) to pass data.

Solution: Use standard C# variables:

// Before (Fluid)
var context = new TemplateContext();
context.SetValue("customer", customerName);

// After (IronPDF)
var customer = customerName;
var html = $"<p>Customer: {customer}</p>";
// Before (Fluid)
var context = new TemplateContext();
context.SetValue("customer", customerName);

// After (IronPDF)
var customer = customerName;
var html = $"<p>Customer: {customer}</p>";
$vbLabelText   $csharpLabel

Issue 3: Thread Safety

Fluid: TemplateContext is not thread-safe, requiring careful management in concurrent applications.

Solution: ChromePdfRenderer is thread-safe and can be shared across threads:

// Thread-safe usage
private static readonly ChromePdfRenderer _renderer = new ChromePdfRenderer();

public byte[] GeneratePdf(string html)
{
    var pdf = _renderer.RenderHtmlAsPdf(html);
    return pdf.BinaryData;
}
// Thread-safe usage
private static readonly ChromePdfRenderer _renderer = new ChromePdfRenderer();

public byte[] GeneratePdf(string html)
{
    var pdf = _renderer.RenderHtmlAsPdf(html);
    return pdf.BinaryData;
}
$vbLabelText   $csharpLabel

Issue 4: Two-Stage Error Handling

Fluid: Errors can occur at templating stage OR PDF generation stage.

Solution: IronPDF has a single error source:

try
{
    var pdf = renderer.RenderHtmlAsPdf(html);
    pdf.SaveAs("output.pdf");
}
catch (Exception ex)
{
    // Single point of failure—easier debugging
    Console.WriteLine($"PDF generation failed: {ex.Message}");
}
try
{
    var pdf = renderer.RenderHtmlAsPdf(html);
    pdf.SaveAs("output.pdf");
}
catch (Exception ex)
{
    // Single point of failure—easier debugging
    Console.WriteLine($"PDF generation failed: {ex.Message}");
}
$vbLabelText   $csharpLabel

Fluid Migration Checklist

Pre-Migration Tasks

Audit your codebase to identify all Fluid usage:

# Find all Fluid references
grep -r "FluidParser\|FluidTemplate\|TemplateContext\|using Fluid" --include="*.cs" --include="*.csproj" .

# Find Liquid template files
find . -name "*.liquid" -o -name "*.html" | xargs grep -l "{{"
# Find all Fluid references
grep -r "FluidParser\|FluidTemplate\|TemplateContext\|using Fluid" --include="*.cs" --include="*.csproj" .

# Find Liquid template files
find . -name "*.liquid" -o -name "*.html" | xargs grep -l "{{"
SHELL

Document all templates: file locations, variables used, loops and conditionals, and external PDF library configuration.

Code Update Tasks

  1. Remove Fluid.Core NuGet package
  2. Remove external PDF library package
  3. Install IronPdf NuGet package
  4. Update namespace imports from Fluid to IronPdf
  5. Convert {{ variable }} to $"{variable}"
  6. Convert {% for item in collection %} to C# foreach
  7. Convert {% if condition %} to C# if statements
  8. Convert Liquid filters to C# methods (e.g., | upcase.ToUpper())
  9. Replace FluidParser with ChromePdfRenderer
  10. Replace TemplateContext.SetValue() with direct C# variables
  11. Remove external PDF library calls
  12. Add IronPDF license initialization at startup

Post-Migration Testing

After migration, verify these aspects:

  • Verify PDF output matches expectations
  • Test all template variations render correctly
  • Check images and styling display properly
  • Validate page breaks occur correctly
  • Test with various data sizes
  • Performance testing vs Fluid + external library
  • Test thread safety in concurrent scenarios

Cleanup Tasks

  • Delete .liquid template files (if no longer needed)
  • Remove Fluid-related helper code
  • Update documentation
  • Clean up unused dependencies

Key Benefits of Migrating to IronPDF

Moving from Fluid (templating) with external PDF libraries to IronPDF provides several critical advantages:

Single Package Solution: Eliminate the two-library dependency. IronPDF handles both templating (via HTML/CSS) and PDF generation in one package.

No New Syntax to Learn: Use standard C# string interpolation and control flow instead of learning Liquid templating syntax.

Thread-Safe Rendering: ChromePdfRenderer is thread-safe, unlike TemplateContext, simplifying concurrent PDF generation.

Chromium Rendering Engine: Industry-standard rendering ensures full CSS3 support including Flexbox and Grid, plus complete JavaScript execution.

Single Error Source: Debugging becomes simpler with only one library to troubleshoot instead of coordinating between templating and PDF generation stages.

Active Development: As .NET 10 and C# 14 adoption increases through 2026, IronPDF's regular updates ensure compatibility with current and future .NET versions.

Curtis Chau
Technical Writer

Curtis Chau holds a Bachelor’s degree in Computer Science (Carleton University) and specializes in front-end development with expertise in Node.js, TypeScript, JavaScript, and React. Passionate about crafting intuitive and aesthetically pleasing user interfaces, Curtis enjoys working with modern frameworks and creating well-structured, visually appealing manuals.

...

Read More