This guide walks you through installing Scriban, writing your first template, and rendering output from C#.
Scriban is available as a NuGet package. Add it to your project with:
dotnet add package Scriban
Scriban targets .NET Standard 2.0+, so it works with .NET 6, .NET 7, .NET 8, .NET 9, .NET 10, .NET Framework 4.7.2+, and more.
A signed variant is also available as Scriban.Signed.
Scriban templates mix plain text with code blocks enclosed in {{ and }}:
using Scriban;
var template = Template.Parse("Hello {{ name }}!");
var result = template.Render(new { Name = "World" });
Console.WriteLine(result);
// Output: Hello World!
That's it - three lines to parse, render, and output a template.
Template.Parse compiles the template string into an internal AST (Abstract Syntax Tree).template.Render evaluates the AST with the given model and produces a string.Name) are automatically exposed as lowercase/snake_case variables (name). See Member renamer to customize this.The simplest way to pass data:
var template = Template.Parse("{{ product.name }} costs {{ product.price }}€");
var result = template.Render(new {
Product = new { Name = "Widget", Price = 9.99 }
});
// Output: Widget costs 9.99€
For dynamic data, use ScriptObject:
using Scriban;
using Scriban.Runtime;
var template = Template.Parse("Hello {{ name }}, you are {{ age }} years old.");
var scriptObject = new ScriptObject();
scriptObject["name"] = "Alice";
scriptObject["age"] = 30;
var context = new TemplateContext();
context.PushGlobal(scriptObject);
var result = template.Render(context);
// Output: Hello Alice, you are 30 years old.
Scriban supports full control flow:
<ul>
{{ for product in products }}
<li>{{ product.name }} - {{ product.price }}€</li>
{{ end }}
</ul>
var template = Template.Parse(@"<ul>
{{ for product in products }}
<li>{{ product.name }} - {{ product.price }}€</li>
{{ end }}
</ul>");
var result = template.Render(new {
Products = new[] {
new { Name = "Apple", Price = 1.20 },
new { Name = "Banana", Price = 0.80 },
new { Name = "Cherry", Price = 2.50 }
}
});
Output:
<ul>
<li>Apple - 1.2€</li>
<li>Banana - 0.8€</li>
<li>Cherry - 2.5€</li>
</ul>
Scriban comes with a rich set of built-in functions for strings, arrays, dates, math, and more. Use the pipe operator | to apply them:
{{ "hello world" | string.capitalize }}
Output: Hello world
{{ [3, 1, 4, 1, 5] | array.sort | array.join ", " }}
Output: 1, 1, 3, 4, 5
{{ date.now | date.to_string '%Y-%m-%d' }}
Output: 2026-02-20 (current date)
You can chain multiple functions with pipes, just like Unix shell commands:
{{ "hello beautiful world" | string.split ' ' | array.reverse | array.join ' ' }}
Output: world beautiful hello
{{ if user.is_admin }}
<p>Welcome, admin!</p>
{{ else }}
<p>Welcome, {{ user.name }}!</p>
{{ end }}
If you are coming from Liquid, Scriban can parse Liquid templates directly:
var template = Template.ParseLiquid("Hello {{ name }}!");
var result = template.Render(new { Name = "World" });
// Output: Hello World!
See the Liquid support guide for the full mapping between Liquid and Scriban syntax.
Scriban fully supports async/await:
var template = Template.Parse("Hello {{ name }}!");
var result = await template.RenderAsync(new { Name = "World" });
This is useful in web applications where you want non-blocking template rendering.
Always check for parsing errors before rendering:
var template = Template.Parse("Hello {{ name }");
if (template.HasErrors)
{
foreach (var message in template.Messages)
{
Console.WriteLine(message);
}
}
else
{
var result = template.Render(new { Name = "World" });
Console.WriteLine(result);
}
Scriban provides precise error messages with line and column numbers.
Starting with Scriban 3.2.1+, you can embed Scriban sources directly into your project instead of referencing the NuGet package as a library. This is useful in contexts where NuGet references are not convenient (e.g., Roslyn Source Generators).
To enable source embedding, make sure your project compiles the embedded sources with C# 9 or later and nullable annotations enabled:
<PropertyGroup>
<PackageScribanIncludeSource>true</PackageScribanIncludeSource>
<LangVersion>9.0</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Scriban" Version="6.5.0" IncludeAssets="Build"/>
</ItemGroup>
In source-embedding mode, all Scriban types become internal.
Scriban.targets already defines SCRIBAN_NO_SYSTEM_TEXT_JSON and SCRIBAN_SOURCE_INCLUDE when PackageScribanIncludeSource is true, so you do not need to add these constants manually.
If your project targets netstandard2.0 or .NET Framework 4.7.2+, also add the supporting packages that Scriban's sources compile against:
<ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
<PackageReference Include="PolySharp" Version="1.15.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
System.Text.Json-based features are intentionally unavailable in source-embedding mode. This includes helpers such as object.from_json, object.to_json, and direct JsonElement import support.
TemplateContext, ScriptObject, custom functions, and advanced scenarios.