A modern .NET library for processing Word (.docx) and LibreOffice / OpenDocument (.odt/.ott) templates without Microsoft Word or LibreOffice
Templify is a focused .NET library that enables dynamic document generation from Word (.docx) and OpenDocument Text (.odt, .ott) templates, and text template processing, through simple placeholder replacement, conditionals, and loops. Unlike complex templating systems, Templify provides an intuitive API for the most common use cases: replacing {{placeholders}} in Word templates with actual data, and generating dynamic text content for emails and notifications.
Key Features:
- π Word (
.docx) and LibreOffice / OpenDocument (.odt,.ott) templates with the same syntax, and aTemplateProcessorthat detects the format - π Simple placeholder syntax:
{{variableName}}, nested paths{{Customer.Address.City}}and indexing{{Items[0].Name}} - π Conditional blocks:
{{#if}}...{{#elseif}}...{{#else}}...{{/if}}, withand/or/not, comparisons,in,contains,exists,is emptyand more - π Loops:
{{#foreach Items}}...{{/foreach}}or{{#foreach item in Items}}...{{/foreach}}, including table row loops and loop metadata (@index,@number,@first,@last,@count) - ποΈ Format specifiers:
{{Amount:currency}},{{Date:date:yyyy-MM-dd}},{{IsActive:checkbox}},{{(Age >= 18):yesno}} - β¨ Markdown formatting in variable values:
**bold**,*italic*,~~strikethrough~~ - β©οΈ Line breaks in variable values:
"Line 1\nLine 2"renders as separate lines - π¨ Automatic formatting preservation (bold, italic, fonts, colors)
- π Processes the body, tables, headers and footers, footnotes and endnotes, text boxes and content controls
- β Template validation and processing warnings, including a Word warning report
- π§ Text template processing for emails and notifications
- π No Microsoft Word or LibreOffice required (OpenXML SDK for Word, plain ZIP/XML for OpenDocument)
Generating Word documents programmatically usually means manual OpenXML manipulation: many lines of code, easy to corrupt documents, and templates that business users cannot maintain. With Templify you:
- Create templates in Word - Use familiar tools, not code
- Add simple placeholders - Just
{{Name}}and{{#if}}...{{/if}} - Process with a few lines of code - Clean, simple API
- Let business users maintain templates - No developer needed
Manual OpenXML vs. Templify
// Manual OpenXML: find and replace text yourself, then handle
// placeholders split across runs, tables, loops and conditionals...
using (var doc = WordprocessingDocument.Open(stream, true))
{
foreach (var text in doc.MainDocumentPart!.Document!.Body!.Descendants<Text>())
{
text.Text = text.Text.Replace("{{Name}}", customerName);
}
// ... many more lines for tables, loops and conditionals
}// Templify
var data = new Dictionary<string, object>
{
["Name"] = customerName,
["Items"] = orderItems,
["IsActive"] = true
};
var processor = new DocumentTemplateProcessor();
processor.ProcessTemplate(templateStream, outputStream, data);dotnet add package TriasDev.Templify-
Create a Word template with placeholders:
Hello {{Name}}! Your order #{{OrderId}} has been confirmed. -
Process it:
using TriasDev.Templify.Core; var data = new Dictionary<string, object> { ["Name"] = "John Doe", ["OrderId"] = "12345" }; var processor = new DocumentTemplateProcessor(); using var templateStream = File.OpenRead("template.docx"); using var outputStream = File.Create("output.docx"); ProcessingResult result = processor.ProcessTemplate(templateStream, outputStream, data); if (!result.IsSuccess) { Console.WriteLine($"Processing failed: {result.ErrorMessage}"); }
-
Done! Open
output.docxand see the result.
The output stream must be readable, writable and seekable (File.Create or a MemoryStream). There are also overloads for files (ProcessTemplateFile), byte arrays, IReadOnlyDictionary<string, object?> data and JSON strings; see the library documentation.
Templates written in LibreOffice Writer (.odt, or .ott templates) use the same syntax. Process them with
OdtTemplateProcessor, or use TemplateProcessor to accept Word and OpenDocument files alike. It detects the format
from the file content:
using TriasDev.Templify.Core;
var processor = new TemplateProcessor(); // .docx, .odt and .ott
ProcessingResult result = processor.ProcessTemplateFile("template.odt", "output.odt", data);An .ott template produces an .odt document. See LibreOffice / OpenDocument Templates for template authors and OpenDocument (.odt) for developers.
Variable values can include markdown syntax for text formatting:
var data = new Dictionary<string, object>
{
["Message"] = "My name is **Alice**" // **bold**
};Supported markdown:
**text**or__text__β Bold*text*or_text_β Italic~~text~~β Strikethrough***text***β Bold + Italic
The markdown formatting is automatically merged with any existing template formatting (e.g., red text + markdown bold = red bold text).
Because any *, _ or ~ can be read as markdown, ordinary data such as my_report_final.docx or 2*3*4 may be reformatted. To insert such values literally:
// Disable markdown for all placeholders
var options = new PlaceholderReplacementOptions { EnableMarkdown = false };or opt out for a single placeholder with the :raw format specifier: {{FileName:raw}} (both since 1.8.0).
Newline characters in variable values are automatically converted to line breaks in Word:
var data = new Dictionary<string, object>
{
["Address"] = "123 Main Street\nApartment 4B\nNew York, NY 10001"
};All newline formats are supported: \n (Unix), \r\n (Windows), \r (old Mac). Newlines work together with markdown: "**Bold line**\n*Italic line*" renders as two lines with proper formatting.
To disable:
var options = new PlaceholderReplacementOptions { EnableNewlineSupport = false };Control how values are displayed using format specifiers (outputs shown for en-US):
{{Name:uppercase}} β ALICE JOHNSON
{{Code:lowercase}} β abc-123
{{Amount:currency}} β $1,234.57 (en-US) or 1.234,57 β¬ (de-DE)
{{Value:number:N2}} β 1,234.57
{{Percentage:number:P2}} β 12.34%
{{OrderDate:date:yyyy-MM-dd}} β 2024-01-15
{{OrderDate:date:MMMM d, yyyy}} β January 15, 2024
{{IsActive:checkbox}} β β or β
{{IsActive:yesno}} β Yes or No
{{FileName:raw}} β my_report_final.docx (no markdown interpretation)
Format specifiers use PlaceholderReplacementOptions.Culture, which defaults to the current culture. Set it explicitly for reproducible output:
var options = new PlaceholderReplacementOptions
{
Culture = new CultureInfo("de-DE") // Affects currency, numbers, dates, and localized boolean words
};
var processor = new DocumentTemplateProcessor(options);Use Templify's condition engine without processing Word documents:
using TriasDev.Templify.Conditionals;
var evaluator = new ConditionEvaluator();
var data = new Dictionary<string, object>
{
["IsActive"] = true,
["Count"] = 5,
["Status"] = "Active"
};
// Single evaluations
bool result = evaluator.Evaluate("IsActive and Count > 0", data);
// Batch evaluation (more efficient for multiple conditions)
var context = evaluator.CreateConditionContext(data);
bool r1 = context.Evaluate("IsActive");
bool r2 = context.Evaluate("IsActive = true");
bool r3 = context.Evaluate("Count > 3");
bool r4 = context.Evaluate("Status in (\"Active\", \"Pending\")");Supported operators: = (or ==), !=, >, <, >=, <=, and, or, not, in, contains, startswith, endswith, exists, is empty, is not empty, and parentheses for grouping. Precedence from lowest to highest: or, and, not, comparisons (including in and the text operators), exists/is empty. So not Status = "Active" means not (Status = "Active"). && and || are not supported.
π Condition Evaluation Guide | Conditionals for template authors
π Full documentation online β (sources in docs/)
- For template authors: Getting Started, Template Syntax, Format Specifiers, LibreOffice / OpenDocument
- For developers: Quick Start, OpenDocument (.odt), Text Templates, Processing Warnings
- Tutorials and FAQ
- Library README - Feature and API reference (also shown on NuGet)
- Examples.md - Extensive code samples and use cases
- ARCHITECTURE.md - Design and implementation
- PERFORMANCE.md - Benchmarks
- CHANGELOG.md - Release history
templify/
βββ TriasDev.Templify/ # Core library (net8.0, net9.0, net10.0), published to NuGet
βββ TriasDev.Templify.Tests/ # xUnit tests for the core library (run on all library TFMs)
βββ TriasDev.Templify.Converter/ # CLI tool: migrate OpenXMLTemplates documents to Templify
βββ TriasDev.Templify.Converter.Tests/ # Tests for the converter
βββ TriasDev.Templify.Gui/ # Cross-platform desktop app for trying templates (Avalonia)
βββ TriasDev.Templify.Demo/ # Demo console application
βββ TriasDev.Templify.DocumentGenerator/# Generates the example templates and outputs used in the docs
βββ TriasDev.Templify.Tools.Tests/ # Tests for the GUI and the document generator
βββ TriasDev.Templify.Benchmarks/ # Performance benchmarks (BenchmarkDotNet)
βββ docs/ # Documentation site (MkDocs)
βββ examples/ # Example templates and generated outputs
βββ scripts/ # Helper scripts for the converter
The template processing library: DocumentTemplateProcessor for Word documents, TextTemplateProcessor for plain text, and ConditionEvaluator for standalone conditions.
Target: net8.0, net9.0, net10.0 (support policy) Dependency: DocumentFormat.OpenXml 3.5.1
π Library Documentation | ποΈ Architecture | π Code Examples
Cross-platform desktop application (Avalonia) to load a template (.docx, .odt or .ott) and JSON data, process the template with Templify, and preview and save the result.
dotnet run --project TriasDev.Templify.Gui/TriasDev.Templify.Gui.csprojSee the GUI README.
Command-line tool for migrating OpenXMLTemplates documents (content controls) to Templify placeholders, with analysis, validation, and cleanup commands.
# Full command
dotnet run --project TriasDev.Templify.Converter/TriasDev.Templify.Converter.csproj -- <command> <document> [options]
# Or use the helper scripts
./scripts/<command>.sh <document> [options] # macOS/Linux
scripts\<command>.cmd <document> [options] # Windows| Command | Purpose |
|---|---|
analyze <template> [--output report.md] |
Inspect an OpenXMLTemplates document and report its content controls |
convert <template> [--output out.docx] [--unwrap-all-controls] |
Convert to Templify syntax (non-OpenXMLTemplates controls are kept unless --unwrap-all-controls) |
validate <document> |
Check that a document is well-formed and can be opened |
clean <document> [--output out.docx] |
Remove all content control (SDT) wrappers |
Options: -o/--output <path>, -v/--verbose. Exit codes: 0 success, 1 the command failed, 2 invalid arguments. The converter does not process templates with data; use the library, the demo (--template/--data) or the GUI for that.
Migration workflow:
./scripts/analyze.sh old-template.docx # 1. analyze
cat old-template-analysis-report.md # 2. review the report
./scripts/convert.sh old-template.docx # 3. convert (writes old-template-templify.docx)
./scripts/validate.sh old-template-templify.docx # 4. validateThe converter translates OpenXMLTemplates content control tags to Templify syntax:
| OpenXMLTemplates | Templify |
|---|---|
variable_CompanyName |
{{CompanyName}} |
conditionalRemove_IsActive |
{{#if IsActive}}...{{/if}} |
conditionalRemove_Count_gt_0 |
{{#if Count > 0}}...{{/if}} |
repeating_LineItems |
{{#foreach LineItems}}...{{/foreach}} |
π Converter Documentation | π Script Usage Guide
Console application that builds a template covering all features, processes it and writes template and output to ./output. It can also process your own files:
dotnet run --project TriasDev.Templify.Demo/TriasDev.Templify.Demo.csproj
dotnet run --project TriasDev.Templify.Demo/TriasDev.Templify.Demo.csproj -- --template my.docx --data my.json [--output out.docx]
dotnet run --project TriasDev.Templify.Demo/TriasDev.Templify.Demo.csproj -- --template my.odt --data my.json # LibreOffice / OpenDocumentPerformance tests with BenchmarkDotNet (placeholders, conditionals, loops, condition engine, complex scenarios):
dotnet run --project TriasDev.Templify.Benchmarks/TriasDev.Templify.Benchmarks.csproj -c Releaseπ Performance Details
- .NET 10 SDK (pinned in
global.json); the library is also built and tested for net8.0 and net9.0, so install those runtimes to run all test targets - Git
- Python 3 (only for building the documentation)
git clone https://github.com/TriasDev/templify.git
cd templify
# Build (Debug); CI builds with warnings as errors:
dotnet build templify.sln
dotnet build templify.sln -c Release -p:ContinuousIntegrationBuild=true
# Run the core tests (all target frameworks, or one with --framework net10.0)
dotnet test TriasDev.Templify.Tests/TriasDev.Templify.Tests.csproj
# Run all test projects
dotnet test templify.sln
# Run specific tests
dotnet test TriasDev.Templify.Tests/TriasDev.Templify.Tests.csproj --filter "FullyQualifiedName~PlaceholderVisitorTests"
# Coverage
dotnet test TriasDev.Templify.Tests/TriasDev.Templify.Tests.csproj --collect:"XPlat Code Coverage"
# Formatting check (required before pushing)
dotnet format --verify-no-changes --no-restoreNuGet versions are managed centrally in Directory.Packages.props, and every project has a committed packages.lock.json. After changing a package, run dotnet restore templify.sln and commit the updated lock files.
pip3 install -r requirements.txt
mkdocs serve # live preview at http://127.0.0.1:8000/templify/
mkdocs build --strict # what CI builds- Read the Contributing Guide and the Code of Conduct
- Add tests for new features and bug fixes, and update the documentation
- Public API changes are tracked in
TriasDev.Templify/PublicAPI.Unshipped.txt - CLAUDE.md has development workflows and architecture notes for AI-assisted coding
- .NET 8.0, 9.0 or 10.0 (the library targets
net8.0,net9.0andnet10.0) - DocumentFormat.OpenXml 3.5.1 (restored automatically)
- GUI: Avalonia 12; tests: xUnit v3; benchmarks: BenchmarkDotNet
The library targets net8.0, net9.0 and net10.0.
Support policy: we support the .NET versions that are in Microsoft support. Target frameworks that reach end of life are dropped in a minor release, announced in the release notes.
β οΈ net6.0 is no longer supported as of 1.8.0. Projects that still target .NET 6 can stay on Templify 1.7.x.
Templify uses a visitor pattern architecture for document processing:
- DocumentWalker - Unified document traversal (body, tables, headers/footers, notes, text boxes, content controls)
- Visitors - ConditionalVisitor, LoopVisitor, PlaceholderVisitor
- Condition engine - Lexer, parser and operator registry shared by
{{#if}}, inline expressions andConditionEvaluator - Evaluation contexts - Hierarchical variable resolution with loop scoping
- PropertyPathResolver - Nested data structure navigation
Processing order: Conditionals β Loops β Placeholders (enables conditionals inside loops and nested loops)
OpenDocument templates are processed by a separate engine (TriasDev.Templify.OpenDocument) with the same processing order and semantics. It reuses the condition engine, placeholder resolution, value conversion and markdown parsing, and leaves the Word pipeline untouched.
ποΈ Full Architecture Documentation
Templify is created and maintained by TriasDev GmbH & Co. KG. It is used in production, processing thousands of documents daily. We believe in giving back to the .NET community and providing a modern, maintainable alternative to legacy Word templating solutions.
Related project: OpenXMLTemplates (predecessor, content-control based); the converter migrates its templates.
Contributions are welcome: bug reports, feature ideas, documentation improvements and pull requests. See the Contributing Guide.
This project is licensed under the MIT License - see the LICENSE file for details.
Copyright Β© 2025 TriasDev GmbH & Co. KG