DEV Community

IronSoftware
IronSoftware

Posted on

Fluid (Templating) vs IronPDF: a technical breakdown for 2026

Teams building document pipelines in .NET often encounter a subtle mismatch: Fluid excels at generating dynamic HTML from templates, while actual PDF creation requires a separate rendering tool. Understanding this distinction becomes critical when logistics platforms need daily shipment manifests, healthcare apps generate HIPAA-compliant reports, or fintech dashboards produce audit trails. Fluid generates the HTML string; something else turns it into a distributable PDF.

IronPDF operates at the opposite end: it's a browser-engine PDF renderer built for converting HTML (from Fluid templates, Razor views, or static files) directly into production-ready PDFs. For workflows where the deliverable is a PDF document—not just templated markup—this fundamental difference shapes every architectural decision.

Understanding IronPDF

IronPDF is a .NET PDF library centered on HTML-to-PDF conversion using a Chromium rendering engine. Teams use it when they need to transform web content—styled with modern CSS Grid, Flexbox, or JavaScript-driven charts—into pixel-perfect PDFs. The library handles the entire PDF lifecycle: creation, editing, form filling, digital signatures, encryption, and page manipulation, all within a single NuGet package.

The core workflow: pass HTML (as a string, file path, or URL) to ChromePdfRenderer, configure print options if needed, and receive a PdfDocument object. From there you can add headers/footers, merge documents, apply watermarks, or save directly to disk or stream. This browser-based rendering ensures your PDF matches what users see in Chrome, which matters when stakeholders review documents before production rollout.

Key Limitations of Fluid (When Used Alone for PDF Generation)

Product Status

Fluid is actively maintained as an open-source template engine. It's not a PDF library and was never designed to produce binary PDF files. Teams sometimes pair Fluid with a PDF renderer (like IronPDF), but Fluid alone outputs text or HTML strings—not PDFs.

Missing Capabilities

No built-in PDF rendering engine. Fluid cannot convert its template output into a PDF without an additional library. It lacks features like page breaks, headers/footers, watermarks, encryption, digital signatures, or form field creation—all standard requirements in document workflows.

Technical Issues

Liquid syntax limitations: no native operators like + or * (use filters: plus:, times:). Null handling uses nil instead of null. While these design choices maintain Liquid's security model (templates can't execute arbitrary code), they add friction when developers expect C#-like expressions. Performance is excellent for template parsing, but complex nested loops with large datasets can slow rendering.

Support Status

Community support via GitHub issues. No commercial SLA or dedicated support team. For production systems requiring guaranteed response times, teams supplement Fluid with commercial tooling elsewhere in the stack.

Architecture Problems

Fluid generates output but doesn't control presentation. CSS must be inline or loaded separately if the HTML goes to a PDF renderer. JavaScript execution depends on the downstream tool—Fluid itself doesn't run JS. This creates a two-stage pipeline where debugging layout issues requires checking both the template logic and the PDF renderer's interpretation of the HTML.

Feature Comparison Overview

Aspect Fluid IronPDF
Current Status Active OSS template engine Commercial PDF library with active development
HTML Support Generates HTML strings Renders HTML to PDF with CSS3/JS support
Rendering Quality N/A (text output only) Chromium-based, pixel-perfect PDF rendering
Installation Single lightweight NuGet Single NuGet + native binaries (self-contained)
Support Community GitHub Commercial support with SLA options
Future Viability Stable for templating Continuous updates for PDF standards

Code Comparison: HTML Template Rendering for Reports

Fluid — Dynamic HTML Template Rendering

using Fluid;
using System;
using System.Collections.Generic;

namespace FluidTemplateExample
{
    class Program
    {
        static void Main(string[] args)
        {
            // Define template source
            var templateSource = @"
                <html>
                <head><title>{{ report.title }}</title></head>
                <body>
                    <h1>{{ report.title }}</h1>
                    <p>Generated: {{ report.date }}</p>
                    <table>
                        <tr><th>Product</th><th>Quantity</th><th>Price</th></tr>
                        {% for item in report.items %}
                        <tr>
                            <td>{{ item.name }}</td>
                            <td>{{ item.quantity }}</td>
                            <td>{{ item.price }}</td>
                        </tr>
                        {% endfor %}
                    </table>
                </body>
                </html>";

            // Parse template
            var parser = new FluidParser();
            if (!parser.TryParse(templateSource, out var template, out var error))
            {
                Console.WriteLine($"Template parse error: {error}");
                return;
            }

            // Prepare data model
            var reportData = new
            {
                report = new
                {
                    title = "Q1 Sales Report",
                    date = DateTime.Now.ToString("yyyy-MM-dd"),
                    items = new[]
                    {
                        new { name = "Widget A", quantity = 100, price = "$50.00" },
                        new { name = "Widget B", quantity = 250, price = "$30.00" },
                        new { name = "Widget C", quantity = 75, price = "$120.00" }
                    }
                }
            };

            // Configure context
            var options = new TemplateOptions();
            options.MemberAccessStrategy.Register(reportData.GetType());
            var context = new TemplateContext(reportData, options);

            // Render to HTML string
            var htmlOutput = template.Render(context);
            Console.WriteLine(htmlOutput);

            // NOTE: htmlOutput is just a string - no PDF yet
            // You would need to pass this to a PDF renderer separately
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Technical limitations with this approach:

  • No PDF output: Fluid stops at HTML string generation; requires a second library to produce PDF
  • Two-stage error handling: Template errors happen at parse time; PDF rendering errors happen later in a separate tool
  • Inline CSS required: For PDF conversion, styles must be inline or embedded since Fluid doesn't manage external assets
  • No page control: Cannot specify page breaks, margins, or headers/footers within Fluid alone
  • Manual integration: Developers must wire Fluid output into a PDF renderer, managing both tool configurations
  • Dependency sprawl: Production apps require Fluid + PDF library + potentially a Razor-to-HTML helper if using MVC

IronPDF — HTML to PDF Rendering (With Template Support)

using IronPdf;
using System;
using System.Text;

namespace IronPdfTemplateExample
{
    class Program
    {
        static void Main(string[] args)
        {
            // License configuration (free 30-day trial available)
            // IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";

            // Build HTML content (could come from Fluid, Razor, or static source)
            var htmlContent = BuildReportHtml();

            // Create PDF renderer
            var renderer = new ChromePdfRenderer();

            // Configure rendering options
            renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4;
            renderer.RenderingOptions.MarginTop = 40;
            renderer.RenderingOptions.MarginBottom = 40;
            renderer.RenderingOptions.PrintHtmlBackgrounds = true;

            // Render HTML to PDF
            var pdf = renderer.RenderHtmlAsPdf(htmlContent);

            // Save PDF
            pdf.SaveAs("Q1_Sales_Report.pdf");
            Console.WriteLine("PDF created successfully");

            // Cleanup
            pdf.Dispose();
        }

        static string BuildReportHtml()
        {
            // In production, this HTML could come from Fluid templates,
            // Razor views, or any HTML generation method
            var sb = new StringBuilder();
            sb.AppendLine("<html><head>");
            sb.AppendLine("<style>body { font-family: Arial; } table { border-collapse: collapse; width: 100%; }");
            sb.AppendLine("th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }</style>");
            sb.AppendLine("</head><body>");
            sb.AppendLine("<h1>Q1 Sales Report</h1>");
            sb.AppendLine($"<p>Generated: {DateTime.Now:yyyy-MM-dd}</p>");
            sb.AppendLine("<table><tr><th>Product</th><th>Quantity</th><th>Price</th></tr>");
            sb.AppendLine("<tr><td>Widget A</td><td>100</td><td>$50.00</td></tr>");
            sb.AppendLine("<tr><td>Widget B</td><td>250</td><td>$30.00</td></tr>");
            sb.AppendLine("<tr><td>Widget C</td><td>75</td><td>$120.00</td></tr>");
            sb.AppendLine("</table></body></html>");
            return sb.ToString();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

IronPDF handles the complete HTML-to-PDF pipeline in one pass. The HTML source is flexible—you can generate it with Fluid if template-driven content makes sense, or use Razor, string builders, or static files. For details on HTML to PDF conversion workflows, the Iron Software documentation covers Razor integration, asset embedding, and layout control.

Code Comparison: Dynamic Form Generation

Fluid — Template-Based Form HTML

using Fluid;
using System;

namespace FluidFormExample
{
    class Program
    {
        static void Main(string[] args)
        {
            var formTemplate = @"
                <html>
                <body>
                    <h2>{{ form.title }}</h2>
                    <form>
                        {% for field in form.fields %}
                        <label>{{ field.label }}</label>
                        <input type='{{ field.type }}' name='{{ field.name }}'
                               placeholder='{{ field.placeholder }}' /><br/>
                        {% endfor %}
                        <button type='submit'>Submit</button>
                    </form>
                </body>
                </html>";

            var parser = new FluidParser();
            if (!parser.TryParse(formTemplate, out var template, out var error))
            {
                Console.WriteLine($"Parse error: {error}");
                return;
            }

            var formModel = new
            {
                form = new
                {
                    title = "Customer Registration",
                    fields = new[]
                    {
                        new { label = "Full Name", type = "text", name = "fullName", placeholder = "John Doe" },
                        new { label = "Email", type = "email", name = "email", placeholder = "john@example.com" },
                        new { label = "Phone", type = "tel", name = "phone", placeholder = "555-1234" }
                    }
                }
            };

            var options = new TemplateOptions();
            options.MemberAccessStrategy.Register(formModel.GetType());
            var context = new TemplateContext(formModel, options);

            var htmlForm = template.Render(context);
            Console.WriteLine(htmlForm);
            // This HTML form still needs a PDF renderer to become a PDF
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Technical limitations:

  • No interactive PDF forms: Output is HTML; to create fillable PDF forms requires a PDF library that understands AcroForm/XFA specs
  • No form field validation: Fluid templates can't enforce PDF-level constraints (regex patterns, required fields, dropdown values)
  • External conversion step: After generating HTML form markup, must hand off to another tool for PDF form creation
  • Limited reusability: HTML forms and PDF forms have different capabilities; template may need rework for PDF output

IronPDF — PDF Form Creation from HTML

using IronPdf;
using System;

namespace IronPdfFormExample
{
    class Program
    {
        static void Main(string[] args)
        {
            var htmlWithForm = @"
                <html>
                <body>
                    <h2>Customer Registration</h2>
                    <form>
                        <label>Full Name:</label>
                        <input type='text' name='fullName' placeholder='John Doe' /><br/>
                        <label>Email:</label>
                        <input type='email' name='email' placeholder='john@example.com' /><br/>
                        <label>Phone:</label>
                        <input type='tel' name='phone' placeholder='555-1234' /><br/>
                        <button type='submit'>Submit</button>
                    </form>
                </body>
                </html>";

            var renderer = new ChromePdfRenderer();
            renderer.RenderingOptions.CreatePdfFormsFromHtml = true; // Convert HTML inputs to PDF form fields

            var pdf = renderer.RenderHtmlAsPdf(htmlWithForm);
            pdf.SaveAs("CustomerRegistrationForm.pdf");

            Console.WriteLine("Interactive PDF form created");
            pdf.Dispose();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

With CreatePdfFormsFromHtml enabled, IronPDF converts HTML form elements into editable PDF fields. For complex scenarios—multi-page forms, calculated fields, digital signatures—refer to IronPDF's form handling documentation.

Code Comparison: Multi-Page Report with Headers/Footers

Fluid — Template with Header/Footer Placeholders

using Fluid;
using System;

namespace FluidMultiPageExample
{
    class Program
    {
        static void Main(string[] args)
        {
            var pageTemplate = @"
                <html>
                <head>
                    <style>
                        @page { margin: 1in; }
                        header { position: running(pageHeader); }
                        footer { position: running(pageFooter); }
                    </style>
                </head>
                <body>
                    <header>{{ header.text }}</header>
                    {% for section in sections %}
                    <div class='page-section'>
                        <h2>{{ section.title }}</h2>
                        <p>{{ section.content }}</p>
                    </div>
                    {% endfor %}
                    <footer>Page {{ footer.pageNumber }}</footer>
                </body>
                </html>";

            var parser = new FluidParser();
            if (!parser.TryParse(pageTemplate, out var template, out var error))
            {
                Console.WriteLine($"Error: {error}");
                return;
            }

            var data = new
            {
                header = new { text = "Annual Report 2025" },
                sections = new[]
                {
                    new { title = "Executive Summary", content = "Lorem ipsum..." },
                    new { title = "Financial Highlights", content = "Revenue grew by..." },
                    new { title = "Future Outlook", content = "We anticipate..." }
                },
                footer = new { pageNumber = "{{pageNumber}}" } // Placeholder - not evaluated
            };

            var options = new TemplateOptions();
            options.MemberAccessStrategy.Register(data.GetType());
            var context = new TemplateContext(data, options);

            var html = template.Render(context);
            Console.WriteLine(html);
            // CSS @page rules won't work in most browsers; PDF renderer must support them
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Technical limitations:

  • No automatic page numbering: Fluid can't inject "page X of Y" without external processing
  • CSS print media dependency: @page rules, running headers, and page breaks require PDF renderer support
  • Manual page break insertion: Developers must guess where content breaks across pages
  • No dynamic header/footer content: Can't access current page number, total pages, or section names without PDF engine support

IronPDF — Native Header/Footer Support

using IronPdf;
using System.Text;

namespace IronPdfHeaderFooterExample
{
    class Program
    {
        static void Main(string[] args)
        {
            var reportHtml = BuildMultiSectionReport();

            var renderer = new ChromePdfRenderer();

            // Configure headers and footers with dynamic content
            renderer.RenderingOptions.TextHeader = new IronPdf.Rendering.TextHeaderFooter
            {
                CenterText = "Annual Report 2025",
                Font = IronPdf.Rendering.FontTypes.Helvetica,
                FontSize = 10
            };

            renderer.RenderingOptions.TextFooter = new IronPdf.Rendering.TextHeaderFooter
            {
                LeftText = "Confidential",
                RightText = "Page {page} of {total-pages}",
                FontSize = 9
            };

            var pdf = renderer.RenderHtmlAsPdf(reportHtml);
            pdf.SaveAs("AnnualReport2025.pdf");

            pdf.Dispose();
        }

        static string BuildMultiSectionReport()
        {
            var sb = new StringBuilder();
            sb.AppendLine("<html><body>");
            sb.AppendLine("<h2>Executive Summary</h2><p>Lorem ipsum dolor sit amet...</p>");
            sb.AppendLine("<div style='page-break-before: always;'></div>");
            sb.AppendLine("<h2>Financial Highlights</h2><p>Revenue grew by 15%...</p>");
            sb.AppendLine("<div style='page-break-before: always;'></div>");
            sb.AppendLine("<h2>Future Outlook</h2><p>We anticipate strong performance...</p>");
            sb.AppendLine("</body></html>");
            return sb.ToString();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

IronPDF's rendering engine understands {page} and {total-pages} tokens, applying them automatically during PDF generation. This eliminates guesswork around pagination.

API Mapping Reference

Fluid Operation Fluid Syntax/Method IronPDF Equivalent Notes
Parse template FluidParser.TryParse() N/A (use HTML directly) Fluid parses Liquid syntax; IronPDF consumes HTML
Render template IFluidTemplate.Render() ChromePdfRenderer.RenderHtmlAsPdf() Fluid outputs string; IronPDF outputs PdfDocument
Set data context TemplateContext HTML content variables Fluid uses context objects; IronPDF uses HTML with embedded data
Loop over items {% for item in items %} Standard HTML/CSS patterns Fluid: template logic; IronPDF: pre-rendered HTML
Conditional rendering {% if condition %} HTML generation logic Evaluate before passing to IronPDF
Access nested properties {{ object.property }} HTML with data Resolve in template or code before rendering
Apply filters `{{ value filter }}` Format data before HTML
Include partial templates {% include 'partial' %} Combine HTML strings Fluid includes files; manually concatenate for IronPDF
Register custom types MemberAccessStrategy.Register() N/A Fluid-specific; IronPDF agnostic to data source
Error handling TryParse(out error) Exception handling on RenderHtmlAsPdf() Different error surfaces
Output to string template.Render() pdf.BinaryData or SaveAs() Fluid: text; IronPDF: binary PDF
Configure options TemplateOptions ChromePdfRenderOptions Separate configuration models

Comprehensive Feature Comparison

Feature Fluid IronPDF
Status
Active Development Yes (OSS) Yes (Commercial)
Latest Release Verify GitHub Regular updates
Support
Community Forums GitHub Issues Yes
Commercial SLA No Available
Documentation Quality Good (Liquid reference) Comprehensive
Code Samples Moderate Extensive
Content Creation
Template Engine Yes (Liquid syntax) No (HTML input)
HTML Output Yes Input only
PDF Output No Yes
Page Breaks No CSS + auto-detection
Headers/Footers No Dynamic tokens
Watermarks No Yes
PDF Operations
Merge PDFs No Yes
Split PDFs No Yes
Extract Text No Yes
Edit Existing PDFs No Yes
Form Filling No Yes
Digital Signatures No Yes
Security
Encryption No AES 128/256-bit
Password Protection No User/Owner passwords
Permissions Control No Print/Copy/Edit restrictions
Known Issues
Operator limitations Uses filter syntax (plus:) instead of + N/A
Null handling Must use nil not null N/A
External CSS Manual inline required for PDF Embedded/external supported
JavaScript execution No Yes (pre-render)
Development
.NET Framework .NET Standard 2.0+ 4.6.2+
.NET Core Yes 3.1+
.NET 5+ Yes Yes
Linux Yes Yes
macOS Yes Yes
Docker Yes Yes

Commonly Reported Issues

Based on community discussions, Fluid users report these patterns:

  1. Learning curve for Liquid syntax: Developers familiar with Razor expect C# expressions; Liquid's filter-based approach requires adjustment
  2. Debugging template errors: Stack traces point to Fluid internals, not template line numbers
  3. Performance with large datasets: Nested loops over thousands of items can be slow
  4. External asset management: Templates can't load CSS/images; must be provided via context
  5. Limited built-in filters: Custom filters require C# code; can't be defined in templates

These are design tradeoffs, not bugs. Verify behavior in your specific use case.

Installation Comparison

Fluid

dotnet add package Fluid
Enter fullscreen mode Exit fullscreen mode
using Fluid;
Enter fullscreen mode Exit fullscreen mode

Lightweight install; no native dependencies.

IronPDF

dotnet add package IronPdf
Enter fullscreen mode Exit fullscreen mode
using IronPdf;
Enter fullscreen mode Exit fullscreen mode

Includes native rendering binaries (Chromium-based engine); larger footprint but self-contained.

Conclusion

Fluid remains a strong choice for teams needing a secure, Shopify-compatible template engine in .NET applications. It's battle-tested in CMS platforms and email generation workflows where the output is HTML or plain text. If your pipeline ends with HTML strings that go to a web browser or email client, Fluid handles that efficiently.

For document workflows where the deliverable is a PDF—invoices, certificates, compliance reports, contracts—the two-stage "Fluid → PDF renderer" approach adds architectural complexity. IronPDF consolidates this into a single library that accepts HTML (from any source) and produces pixel-perfect PDFs with headers, footers, forms, signatures, and encryption. The Chromium engine ensures output matches browser rendering, which matters when design fidelity is non-negotiable.

Migration becomes necessary when: teams maintain both Fluid and a separate PDF library; pagination issues require PDF-aware layout control; or compliance mandates demand native PDF features like digital signatures and redaction. In those cases, IronPDF's unified API reduces dependency sprawl and testing surface area.

Does your current document pipeline split HTML generation from PDF rendering, or do you handle both in a single pass? How do you manage page breaks and headers across large reports?

Explore IronPDF's Chromium-based rendering capabilities: HTML to PDF conversion guide
Review IronPDF's complete feature set: C# PDF creation tutorial

Top comments (0)