ZingPDF logo

Guide

How to create a PDF from a Liquid HTML template in C#

Reference ZingPDF.Templates.LiquidHtml, render a Liquid template with a .NET model, and write the converted PDF stream.

The package uses Fluid for Liquid rendering and ZingPDF.FromHTML for HTML-to-PDF conversion. It has the same browser automation requirements as ZingPDF.FromHTML.

Render the model to PDF

LiquidHtmlPdfTemplate.FromFile(...) reads the template, renders it with the supplied model, converts the rendered HTML to PDF, and copies the PDF bytes to the output stream.

using ZingPDF.Templates.LiquidHtml;

var invoice = new
{
    Number = "INV-1001",
    CustomerName = "Ada Lovelace",
    Items = new[]
    {
        new { Description = "Consulting", Total = 240m },
        new { Description = "Support", Total = 80m }
    }
};

await using var output = File.Create("invoice.pdf");

await LiquidHtmlPdfTemplate
    .FromFile("invoice.liquid.html")
    .RenderAsync(invoice, output);

Share an installed browser

Use the invoice model above with a configured renderer. Set ZINGPDF_BROWSER_PATH to an installed browser and supply the application's cancellationToken.

using ZingPDF.FromHTML;
using ZingPDF.Templates;
using ZingPDF.Templates.LiquidHtml;

await using var renderer = new HtmlToPdfRenderer(new HtmlToPdfOptions
{
    BrowserExecutablePath = Environment.GetEnvironmentVariable("ZINGPDF_BROWSER_PATH")
        ?? throw new InvalidOperationException("Set ZINGPDF_BROWSER_PATH to an installed browser.")
});

var template = LiquidHtmlPdfTemplate.FromSource(
    PdfTemplateSource.FromFile("invoice.liquid.html"),
    new RendererHtmlToPdfConverter(renderer));

await using var output = File.Create("invoice.pdf");
await template.RenderAsync(invoice, output, cancellationToken: cancellationToken);

The template forwards cancellation to the converter and output copy. The renderer deadline starts after Liquid rendering. The template and adapter do not dispose the shared renderer. Liquid parsing and rendering are not an interruptible OS-level workload.

Write a Liquid HTML template

Liquid variables, loops, and conditionals are evaluated before the HTML is sent to the PDF converter.

<!doctype html>
<html>
  <head>
    <style>
      @page { size: A4; margin: 15mm; }
      body { font-family: Arial, sans-serif; margin: 0; }
      thead { display: table-header-group; }
      tr { break-inside: avoid; }
      table { border-collapse: collapse; width: 100%; }
      th, td { border-bottom: 1px solid #ddd; padding: 8px; text-align: left; }
      .amount { text-align: right; }
    </style>
  </head>
  <body>
    <h1>Invoice {{ Number }}</h1>
    <p>Bill to: {{ CustomerName }}</p>

    <table>
      <thead>
        <tr>
          <th>Description</th>
          <th class="amount">Total</th>
        </tr>
      </thead>
      <tbody>
        {% for item in Items %}
        <tr>
          <td>{{ item.Description }}</td>
          <td class="amount">{{ item.Total }}</td>
        </tr>
        {% endfor %}
      </tbody>
    </table>
  </body>
</html>

Inspect the rendered HTML

Use RenderHtmlAsync(...) when you want to inspect Liquid output before browser conversion.

var html = await LiquidHtmlPdfTemplate
    .FromFile("invoice.liquid.html")
    .RenderHtmlAsync(invoice);

await File.WriteAllTextAsync("invoice-preview.html", html);

Use includes from the template directory

File-backed templates resolve Liquid includes from the template file directory unless LiquidHtmlPdfTemplateOptions.BasePath is set.

{% include 'line-item.liquid' %}
await LiquidHtmlPdfTemplate
    .FromFile("templates/invoice.liquid.html")
    .RenderAsync(invoice, output, new LiquidHtmlPdfTemplateOptions
    {
        BasePath = "templates/partials"
    });

Account for browser conversion

  • Supply an installed Chromium-compatible executable through BrowserExecutablePath. Browser downloading is disabled by default; AllowBrowserDownload enables it explicitly.
  • Reuse HtmlToPdfRenderer across jobs. One browser serves isolated contexts; MaxConcurrency defaults to four and additional jobs queue.
  • The new renderer's default 30-second RenderTimeout covers queueing, startup, loading, font readiness and printing. Timeout.InfiniteTimeSpan disables the overall deadline. The separate BrowserStartupTimeout defaults to 30 seconds and must remain finite. Expiry throws TimeoutException; caller cancellation throws OperationCanceledException. Context creation and cleanup must finish; browser startup or downloading can remain renderer-owned after a caller stops waiting, within the separate startup budget. Disposal cancels browser provisioning. The deadline is not an OS-enforced hard worker bound.
  • Options are copied at construction. The caller disposes the renderer and returned streams. New defaults use print media, background printing and CSS page sizes; customize PdfOptions for paper, margins, headers or page ranges.
  • Original static Converter.ToPdfAsync(...) overloads retain automatic browser downloading, screen media and default PuppeteerSharp print settings. They disable the overall deadline to preserve explicit navigation timeouts, including zero; the browser startup budget remains finite. The overload taking HtmlToPdfOptions uses explicit settings for one job.
  • Offline emulates offline page networking. Embed fonts and images as data URLs. It is not a network sandbox or security guarantee. Accept trusted HTML and scripts; isolated contexts do not replace OS process isolation.
  • Install the browser, OS libraries and fonts with the deployment. The renderer does not disable Chromium's sandbox by default.

Default FromFile, FromString and single-argument FromSource use the original static converter. Use the two-argument FromSource overload for explicit deployment settings.

GenerateInvoices supplies a long-table template with 0, 1, 40 and 500 rows, repeated headers, totals and embedded fonts. Validate the recipe in the target Linux image.

Need PDF-native fields instead?

AcroForm filling uses an existing PDF form as the template and does not use HTML conversion.

Open form guide Open template docs