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
}
}
}
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();
}
}
}
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
}
}
}
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();
}
}
}
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
}
}
}
Technical limitations:
- No automatic page numbering: Fluid can't inject "page X of Y" without external processing
-
CSS print media dependency:
@pagerules, 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();
}
}
}
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:
- Learning curve for Liquid syntax: Developers familiar with Razor expect C# expressions; Liquid's filter-based approach requires adjustment
- Debugging template errors: Stack traces point to Fluid internals, not template line numbers
- Performance with large datasets: Nested loops over thousands of items can be slow
- External asset management: Templates can't load CSS/images; must be provided via context
- 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
using Fluid;
Lightweight install; no native dependencies.
IronPDF
dotnet add package IronPdf
using IronPdf;
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)