Skip to content

Templating — Scriban Text & Email Templates

Most applications start with hardcoded strings in email services — $"Dear {name}, your order {id} has shipped". This works until the business wants to change the wording without a deployment, support multiple languages, or require approval before a customer communication goes live. Suddenly you need a template store, a rendering engine, variable injection, culture fallback, and an audit trail — all built in-house under pressure.

Granit.Templating provides this entire stack out of the box. Templates are versioned entities with a full ISO 27001 lifecycle (Draft, PendingReview, Published, Archived) — so business users can edit and preview templates while compliance teams approve changes before they reach customers. The Scriban engine runs in a sandbox (no I/O, no reflection), making it safe even with user-authored templates. Culture fallback, global context injection, and CQRS store access are built into the pipeline, not bolted on after the fact.

For binary document output (PDF, Excel), see Document Generation.

  • DirectoryGranit.Templating/ Core pipeline: ITextTemplateRenderer, resolvers, engines, enrichers, global contexts, template store CQRS
    • Granit.Templating.Scriban Scriban engine (sandboxed, no I/O or reflection), template caching, built-in global contexts, include support
    • Granit.Templating.EntityFrameworkCore EF Core persistence (TemplatingDbContext), FusionCache-backed store, StoreTemplateResolver
    • Granit.Templating.Endpoints 17 admin endpoints (CRUD, preview, publish, categories, layouts, history, variables)
    • Granit.Templating.Mjml MJML email template compiler (Mjml.Net, zero Node.js)
    • Granit.Templating.Workflow Bridge to Granit.Workflow: FSM validation, approval routing, unified audit trail
PackageRoleDepends on
Granit.TemplatingCore pipeline: rendering, resolution, enrichment, layout registry, post-render transformer pipeline, store interfacesGranit.Workflow
Granit.Templating.ScribanScriban template engine (sandboxed), now.*, context.* and app.* globals, {{ include }} supportGranit.Templating
Granit.Templating.MjmlMJML-to-HTML compiler: table-based layout, inline CSS, MSO conditionals. Zero Node.js.Granit.Templating
Granit.Templating.EntityFrameworkCoreEF Core store, StoreTemplateResolver (Priority=100), FusionCache, WorkflowTransitionRecord audit trailGranit.Templating, Granit.Persistence, Granit.Workflow.EntityFrameworkCore
Granit.Templating.Endpoints17 admin Minimal API endpoints, Templates.Manage permissionGranit.Templating, Granit.Authorization
Granit.Templating.WorkflowWorkflow bridge: FSM validation, approval routingGranit.Templating, Granit.Workflow
graph TD
    T[Granit.Templating] --> W[Granit.Workflow]
    TS[Granit.Templating.Scriban] --> T
    TM[Granit.Templating.Mjml] --> T
    TEF[Granit.Templating.EntityFrameworkCore] --> T
    TEF --> P[Granit.Persistence]
    TEF --> WEF[Granit.Workflow.EntityFrameworkCore]
    TE[Granit.Templating.Endpoints] --> T
    TE --> AUTH[Granit.Authorization]
    TW[Granit.Templating.Workflow] --> T
    TW --> W
    DG[Granit.DocumentGeneration] --> T
    DGP[Granit.DocumentGeneration.Pdf] --> DG
    DGE[Granit.DocumentGeneration.Excel] --> T
[DependsOn(
typeof(GranitTemplatingEndpointsModule),
typeof(GranitTemplatingEntityFrameworkCoreModule),
typeof(GranitTemplatingScribanModule))]
public class AppModule : GranitModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddGranitTemplatingEntityFrameworkCore(options =>
options.UseNpgsql(context.Configuration.GetConnectionString("Templating")));
}
}

Map the admin endpoints in Program.cs:

app.MapGranitTemplating(options =>
{
options.RoutePrefix = "api/v1/templates";
options.TagName = "Template Administration";
});