Common questions and answers about using Templify for Word document templating.
- Getting Started
- Features & Capabilities
- Syntax & Usage
- Performance
- Troubleshooting
- Migration
- Enterprise & Production
- Comparison
A: Templify is a modern .NET library for generating Word documents from templates. You create a Word template with placeholders like {{Name}}, and Templify replaces them with your data. It supports conditionals, loops, and nested data structures without requiring Microsoft Word to be installed.
A:
- .NET 6.0 or later (supports .NET 6.0, 8.0, and 9.0)
- DocumentFormat.OpenXml 3.3.0 (automatically installed via NuGet)
- Any platform supported by .NET (Windows, macOS, Linux)
- No Microsoft Word installation required
A: Via NuGet:
dotnet add package TriasDev.TemplifyOr in Visual Studio: Install-Package TriasDev.Templify
A: Follow this learning path:
- Quick Start Guide (5 minutes)
- Tutorial 1: Hello World (30 min)
- Tutorial 2: Invoice Generator (1 hour)
- Full API Documentation
A: No! Templify uses the OpenXML SDK to work directly with .docx files. It runs on any platform without Office installed.
A: Yes! Templify works perfectly in:
- ASP.NET Core web APIs
- Blazor applications
- Azure Functions
- Background job processors
- Desktop applications
- Console applications
A: Core features:
- ✅ Placeholders:
{{Name}},{{User.Email}} - ✅ Nested data:
{{Company.Address.City}} - ✅ Array indexing:
{{Items[0].Name}} - ✅ Boolean format specifiers:
{{IsActive:checkbox}},{{IsVerified:yesno}} - ✅ Boolean expressions:
{{(Age >= 18 and HasLicense):yesno}} - ✅ Conditionals:
{{#if IsActive}}...{{#else}}...{{/if}} - ✅ Loops:
{{#foreach Items}}...{{/foreach}} - ✅ Nested loops: Loops inside loops (arbitrary depth)
- ✅ Table row loops: Dynamic table generation
- ✅ Loop variables:
@index,@first,@last,@count - ✅ Formatting preservation: Bold, italic, colors, fonts maintained
- ✅ Comparison operators:
>,<,>=,<=,==,!= - ✅ Logical operators:
and,or,not - ✅ Localization: Format specifiers adapt to cultures (en, de, fr, es, it, pt)
- ✅ JSON support: Use JSON data instead of C# dictionaries
- ✅ Type coercion: Automatic number/date conversions
A: Current limitations:
- ❌ Mathematical expressions in templates: Can't do
{{Price * Quantity}}(calculate in code) - ❌ String manipulation in templates: Can't do
{{Name.ToUpper()}}(format in code) - ❌ Image insertion: No direct image placeholders yet
- ❌ Chart/graph generation: Not supported
- ❌ Macro execution: Security limitation
- ❌ Form fields: Use placeholders instead
- ❌ Complex formatting changes: Background colors, page breaks (use template structure)
Workaround: Calculate/format values in your C# code before passing to Templify.
A: Yes! Templify is designed for performance:
- Processes 1,000+ placeholders in ~100ms
- Handles documents with hundreds of pages
- Low memory footprint
- See Performance Benchmarks
A: Yes! Full table support:
- Replace placeholders in table cells
- Loop over table rows:
{{#foreach Items}} - Nested tables
- Conditional rows
- Formatting preservation
A: Absolutely! Common mail merge scenario:
var recipients = GetRecipients(); // List of people
foreach (var recipient in recipients)
{
var data = new Dictionary<string, object>
{
["Name"] = recipient.Name,
["Address"] = recipient.Address,
["City"] = recipient.City
};
using var templateStream = File.OpenRead("letter-template.docx");
using var outputStream = File.Create($"letter-{recipient.Id}.docx");
processor.ProcessTemplate(templateStream, outputStream, data);
}A: Use double curly braces:
- Simple:
{{Name}} - Nested:
{{User.Email}} - Array:
{{Items[0]}} - With spaces:
{{ Name }}(spaces ignored)
Rules:
- Letters, numbers, underscore, dot, brackets only
- Case-sensitive
- No special characters in variable names
A: Use dot notation:
Data:
var data = new Dictionary<string, object>
{
["Company"] = new
{
Name = "Acme Corp",
Address = new
{
Street = "123 Main St",
City = "Springfield"
}
}
};Template:
Company: {{Company.Name}}
Location: {{Company.Address.City}}
A: Use {{#if}}...{{#else}}...{{/if}}:
{{#if IsActive}}
This customer is active.
{{#else}}
This customer is inactive.
{{/if}}
Supported conditions:
- Boolean:
{{#if IsActive}} - Comparison:
{{#if Age > 18}} - Null check:
{{#if Email}}(true if not null/empty) - Combined:
{{#if Age >= 18 && HasLicense}}
A: Use {{#foreach}}...{{/foreach}}:
Template:
{{#foreach Items}}
- {{Name}}: {{Price}} EUR
{{/foreach}}
Data:
["Items"] = new List<object>
{
new { Name = "Product A", Price = 10.00m },
new { Name = "Product B", Price = 20.00m }
}Output:
- Product A: 10.00 EUR
- Product B: 20.00 EUR
A: Inside loops, use these:
{{@index}}- Current position (0-based){{@first}}- True for first item{{@last}}- True for last item{{@count}}- Total number of items
Example:
{{#foreach Items}}
Item {{@index}}: {{Name}}{{#if @last}} (last){{/if}}
{{/foreach}}
A: Yes! Unlimited nesting:
{{#foreach Orders}}
Order #{{OrderId}}:
{{#foreach Items}}
- {{Product}}: {{Quantity}} x {{Price}}
{{/foreach}}
{{/foreach}}
A: Place loop markers in table rows:
| Product | Quantity | Price |
|---|---|---|
| {{#foreach Items}} | ||
| {{Name}} | {{Quantity}} | {{Price}} |
| {{/foreach}} |
Templify will repeat the row for each item.
A: Check ProcessingResult:
var result = processor.ProcessTemplate(templateStream, outputStream, data);
if (result.MissingVariables.Any())
{
Console.WriteLine("Warning - variables not found:");
foreach (var variable in result.MissingVariables)
{
Console.WriteLine($" - {variable}");
}
}Missing variables are left as-is: {{MissingVar}} remains in output.
A: Use format specifiers with the :format syntax:
Template:
Task complete: {{IsCompleted:checkbox}}
Approved: {{IsApproved:yesno}}
Valid: {{IsValid:checkmark}}
C# Data:
var data = new Dictionary<string, object>
{
["IsCompleted"] = true,
["IsApproved"] = false,
["IsValid"] = true
};JSON Data:
{
"IsCompleted": true,
"IsApproved": false,
"IsValid": true
}Output:
Task complete: ☑
Approved: No
Valid: ✓
Available formatters:
checkbox→ ☑/☐yesno→ Yes/Nocheckmark→ ✓/✗truefalse→ True/Falseonoff→ On/Offenabled→ Enabled/Disabledactive→ Active/Inactive
See the Format Specifiers Guide for complete documentation.
A: Yes! While Templify processes C# Dictionary<string, object> internally, JSON can be easily converted:
JSON Data File (data.json):
{
"CompanyName": "Acme Corp",
"IsActive": true,
"Items": [
{ "Name": "Product A", "Price": 10.00 },
{ "Name": "Product B", "Price": 20.00 }
]
}C# Code:
using System.Text.Json;
// Read and deserialize JSON
string jsonText = File.ReadAllText("data.json");
var data = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonText);
// Process template
var processor = new DocumentTemplateProcessor();
processor.ProcessTemplate(templateStream, outputStream, data);JSON is particularly useful for:
- Business users providing data
- API integrations
- Configuration files
- Testing with sample data
A: Use parentheses to evaluate logic directly in placeholders:
Template:
Eligible: {{(Age >= 18):yesno}}
Access granted: {{(IsActive and IsVerified):checkbox}}
Can proceed: {{(HasPermissionA or HasPermissionB):checkmark}}
Data:
{
"Age": 25,
"IsActive": true,
"IsVerified": true,
"HasPermissionA": false,
"HasPermissionB": true
}Output:
Eligible: Yes
Access granted: ☑
Can proceed: ✓
Supported operators:
- Logical:
and,or,not - Comparison:
=,==,!=,>,>=,<,<= - Nested:
((var1 or var2) and var3)
See the Boolean Expressions Guide for complete documentation.
A: Yes! This is one of the most powerful features:
Template:
Qualified driver: {{((Age >= 18) and (HasLicense or HasPermit)):yesno}}
Data:
{
"Age": 20,
"HasLicense": false,
"HasPermit": true
}Output:
Qualified driver: Yes
More examples:
Over budget: {{(Spent > Budget):checkbox}}
Status OK: {{(not HasErrors):checkmark}}
Premium member: {{(MembershipLevel >= 3):enabled}}
A: Register custom formatters with the registry:
var registry = new BooleanFormatterRegistry();
registry.Register("thumbs", new BooleanFormatter("👍", "👎"));
registry.Register("traffic", new BooleanFormatter("🟢", "🔴"));
var options = new PlaceholderReplacementOptions
{
BooleanFormatterRegistry = registry
};
var processor = new DocumentTemplateProcessor(options);Template:
User feedback: {{IsPositive:thumbs}}
System status: {{IsOperational:traffic}}
Output:
User feedback: 👍
System status: 🟢
A: Yes! Format specifiers automatically adapt to the culture:
German Output:
var options = new PlaceholderReplacementOptions
{
Culture = new CultureInfo("de-DE"),
BooleanFormatterRegistry = new BooleanFormatterRegistry(new CultureInfo("de-DE"))
};
var processor = new DocumentTemplateProcessor(options);Template: {{IsActive:yesno}}
Output (de-DE): Ja or Nein
Output (fr-FR): Oui or Non
Output (es-ES): Sí or No
Supported languages for yesno:
- English (en): Yes/No
- German (de): Ja/Nein
- French (fr): Oui/Non
- Spanish (es): Sí/No
- Italian (it): Sì/No
- Portuguese (pt): Sim/Não
Symbol-based formatters (checkbox, checkmark) are universal.
A: Very fast! Benchmark results:
- Simple replacement: ~0.5ms for 10 placeholders
- Complex document: ~100ms for 1,000 placeholders
- Table with loops: ~50ms for 100 rows
See detailed Performance Benchmarks.
A: No internal caching. Best practice:
- For repeated processing, reuse the
DocumentTemplateProcessorinstance - Cache template streams in your application if needed
- Process templates asynchronously for better throughput
A: Yes! DocumentTemplateProcessor is thread-safe for reading. Best practice:
var processor = new DocumentTemplateProcessor();
Parallel.ForEach(dataList, data =>
{
using var templateStream = GetTemplateStream(); // Separate stream per thread
using var outputStream = GetOutputStream();
processor.ProcessTemplate(templateStream, outputStream, data);
});Important: Each thread needs its own template/output streams.
A: Tips:
- Reuse processor: Create once, use many times
- Minimize template complexity: Fewer loops = faster
- Pre-calculate values: Don't use complex nested paths
- Use memory streams: Faster than file I/O
- Batch processing: Process multiple templates in parallel
A: Common causes:
- Typo in placeholder name:
{{Nmae}}vs{{Name}}(case-sensitive!) - Data not provided: Check
result.MissingVariables - Wrong data structure: Verify nested paths match your object
- Word formatting broke placeholder: Word sometimes splits
{{Name}}into multiple runs
Fix #4: Select the placeholder in Word and clear formatting (Ctrl+Space), then retype it.
A: Word's internal format sometimes splits text. Fix:
- Select the placeholder in Word
- Press Ctrl+Space (remove formatting)
- Retype the placeholder without formatting changes mid-text
A: Check:
- Condition syntax:
{{#if IsActive}}not{{if IsActive}} - Variable exists: Provide the variable in data
- Type mismatch:
"true"(string) is nottrue(boolean) - Comparison operators: Use
=or==for equality (both are supported) - Closing tag: Must have
{{/if}}
A: Checklist:
- Collection exists: Verify data contains the collection
- Collection is enumerable: Use
List<T>,T[], orIEnumerable<T> - Closing tag: Must have
{{/foreach}} - Correct variable name: Case-sensitive!
A: Common causes:
- Stream not disposed: Use
usingstatements - Stream position not reset: Call
stream.Position = 0before processing - Concurrent access: Don't share streams between threads
- Template already corrupted: Validate template with Converter tool
Validate template:
dotnet run --project TriasDev.Templify.Converter -- validate template.docxA: Steps:
-
Check
ProcessingResult:if (!result.IsSuccessful) { foreach (var error in result.Errors) Console.WriteLine(error); }
-
Review missing variables:
foreach (var missing in result.MissingVariables) Console.WriteLine($"Missing: {missing}");
-
Simplify template: Remove complexity until it works, then add back
-
Check template structure: Use Converter's analyze command:
dotnet run --project TriasDev.Templify.Converter -- analyze template.docx
A: Yes! Use the Converter tool:
dotnet run --project TriasDev.Templify.Converter -- analyze template.docx --output report.mdShows all placeholders, conditionals, and loops found.
A: Templify has a built-in converter!
Step 1: Analyze your template:
./scripts/analyze.sh old-template.docxStep 2: Convert:
./scripts/convert.sh old-template.docxStep 3: Update code:
// Old: OpenXMLTemplates
var processor = new TemplateProcessor("template.docx");
processor.SetValue("variable_Name", "John");
// New: Templify
var processor = new DocumentTemplateProcessor();
var data = new Dictionary<string, object> { ["Name"] = "John" };
processor.ProcessTemplate(templateStream, outputStream, data);See full Converter Documentation.
| OpenXMLTemplates | Templify |
|---|---|
variable_Name |
{{Name}} |
conditionalRemove_IsActive |
{{#if IsActive}}...{{/if}} |
conditionalRemove_Count_gt_0 |
{{#if Count > 0}}...{{/if}} |
repeating_Items |
{{#foreach Items}}...{{/foreach}} |
A: Yes! Benefits:
- 90% less code: Typical reduction
- No XML knowledge needed: Work with Word templates
- Easier maintenance: Templates updated by non-developers
- Fewer bugs: No manual XML manipulation
Before (manual OpenXML):
// 50+ lines of XML manipulation
using (var doc = WordprocessingDocument.Open(...))
{
var body = doc.MainDocumentPart.Document.Body;
foreach (var text in body.Descendants<Text>())
{
if (text.Text.Contains("{{Name}}"))
{
text.Text = text.Text.Replace("{{Name}}", name);
}
}
// ... many more lines
}After (Templify):
// 3 lines!
var data = new Dictionary<string, object> { ["Name"] = name };
processor.ProcessTemplate(templateStream, outputStream, data);A: Yes! Templify is battle-tested in production environments, processing thousands of documents daily.
Quality indicators:
- ✅ 109 tests, 100% code coverage
- ✅ Battle-tested in enterprise environment
- ✅ Comprehensive error handling
- ✅ Performance optimized
- ✅ Well-documented
A: Templify is designed with security in mind:
- ✅ No code execution: Templates are data, not code
- ✅ No external dependencies: Only OpenXML SDK
- ✅ Input validation: Validates template structure
- ✅ No macro execution: Security by design
- ✅ Safe XML processing: Protection against XXE attacks
- ✅ Memory limits: Protection against zip bombs
Best practices:
- Validate user-uploaded templates before processing
- Sanitize user input before passing to templates
- Set resource limits for large-scale processing
- Keep OpenXML SDK updated
A: [License information to be added] - See LICENSE file in repository.
A: Templify is maintained by TriasDev GmbH & Co. KG. For enterprise support:
- 📧 Contact: [contact information to be added]
- 💬 Community support: GitHub Discussions
- 🐛 Bug reports: GitHub Issues
A: Yes! Templify can be used in commercial applications. Check the LICENSE file for specific terms.
A: Robust error handling:
try
{
var result = processor.ProcessTemplate(templateStream, outputStream, data);
if (!result.IsSuccessful)
{
// Log errors for investigation
_logger.LogError("Template processing failed: {Errors}",
string.Join(", ", result.Errors));
// Optionally notify user
return BadRequest("Document generation failed");
}
if (result.MissingVariables.Any())
{
// Log warnings
_logger.LogWarning("Missing template variables: {Variables}",
string.Join(", ", result.MissingVariables));
}
return File(outputStream.ToArray(), "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error processing template");
return StatusCode(500, "Internal server error");
}A: Track these metrics:
var sw = Stopwatch.StartNew();
var result = processor.ProcessTemplate(templateStream, outputStream, data);
sw.Stop();
_logger.LogInformation(
"Template processed: {Duration}ms, Placeholders: {Count}, Size: {Size}KB",
sw.ElapsedMilliseconds,
result.PlaceholdersReplaced,
outputStream.Length / 1024
);
// Track in your monitoring system (Application Insights, Prometheus, etc.)
_metrics.RecordDuration("templify.processing", sw.ElapsedMilliseconds);
_metrics.RecordCount("templify.placeholders", result.PlaceholdersReplaced);A: Advantages:
| Aspect | Manual OpenXML | Templify |
|---|---|---|
| Code complexity | High (50-200 lines) | Low (5-10 lines) |
| Learning curve | Steep (XML knowledge) | Gentle (just placeholders) |
| Template creation | Programmatic | Visual (in Word) |
| Maintenance | Difficult | Easy |
| Non-developer friendly | No | Yes |
| Error-prone | High | Low |
| Performance | Similar | Optimized |
A:
| Feature | Templify | DocX |
|---|---|---|
| Focus | Template processing | Document creation |
| Use case | Fill templates | Build docs from scratch |
| Conditionals | ✅ Built-in | ❌ Code only |
| Loops | ✅ Built-in | ❌ Code only |
| Template syntax | ✅ Simple {{}} |
❌ N/A |
| Learning curve | Low | Medium |
Choose Templify when: You have Word templates to fill Choose DocX when: You're building documents programmatically from scratch
A:
| Aspect | Templify | XSLT |
|---|---|---|
| Template format | Word .docx | XML |
| Readability | High (visual) | Low (code-like) |
| Designer-friendly | Yes | No |
| Complexity | Simple | Complex |
| Performance | Fast | Slower |
| Ecosystem | .NET | Cross-platform |
A: Use alternatives when:
- Generating PDFs directly: Use PDF library (e.g., iText)
- Complex layouts from scratch: Use DocX or direct OpenXML
- Real-time collaborative editing: Use Office Online
- Very simple text substitution: Use
string.Replace() - Excel files: Use EPPlus or ClosedXML
- Non-.NET environment: Use platform-specific solutions
A: Partially. Comparison:
| Feature | Templify | Crystal Reports |
|---|---|---|
| Data binding | ✅ Manual | ✅ Automatic |
| Designer | ✅ Word | ✅ Proprietary |
| Conditionals | ✅ Yes | ✅ Yes |
| Loops | ✅ Yes | ✅ Yes |
| Charts/Graphs | ❌ No | ✅ Yes |
| Grouping | ✅ Automatic | |
| Export formats | Word only | Multiple |
| Cost | Free/Open-source | Commercial |
Use Templify for: Document-centric reports (contracts, letters, proposals) Use Crystal Reports for: Data-heavy reports with charts and complex grouping
- 💬 GitHub Discussions - Ask the community
- 🐛 GitHub Issues - Report bugs
- 📖 Full Documentation - Complete reference
Open a discussion or create an issue on GitHub.
Last Updated: 2025-01-15