Overview
An overview of the functionality and the modifier functions.
A zero-dependency templating engine for Go, built for workflows, document generation, and data transformations.
{{ response | from_json | key:'orders' | filter:'status','paid' | pluck:'total' | sum | decimal:2 }}That one line takes a raw JSON response, drills into the orders, keeps the paid ones, pulls out every total, adds them up, and formats the result to two decimals. No Go code, no helper functions, no glue.
Getting started
go get github.com/toaweme/sintaximport (
"github.com/toaweme/sintax"
"github.com/toaweme/sintax/defaults"
)
out, err := sintax.Render(
"{{ users | filter:'active',true | pluck:'email' }}",
vars,
defaults.All(),
)Here out is the value the template produced, not text. Render hands back any, so a pipeline that ends in a slice of emails gives you that slice with its type intact. When text is what you want, call RenderString instead and sintax formats the result for you.
The full source lives on GitHub. For the Go API in depth, see the Quick Guide.
The problem
Go's text/template was built to render HTML and text. The moment you need to actually transform data, filter a slice, sum a column, parse embedded JSON, you leave the template and write Go.
Sintax moves the transformation into the template, where you can read it top to bottom.
Why sintax
- Familiar syntax. Jinja2-style pipes that read left to right. If you have written a Django or Ansible template, you already know it.
- 57 built-in modifiers. String operations, collection operations, conversion, formatting, file paths, and defaults, all shipping in the box.
text/templateships around 14. - Returns values, not just text.
Renderreturnsany. A template that resolves to a slice gives you a slice, with its type intact, not a stringified version of one. Reach forRenderStringwhen the output is a document and you want text. - Typed errors. Parse failures, missing variables, and circular references surface as distinct sentinel errors you can branch on with
errors.Is. - Dependency resolution. Variables can reference each other, and sintax resolves them in the correct order. Circular references are caught at resolution time, not at runtime.
- Zero dependencies. The core imports only the standard library. Nothing to audit, nothing to keep offline, nothing that ships a transitive tree behind your back.
- Extensible. Register your own modifiers as a single function type, or override a built-in by name.
What it looks like
Chain modifiers to transform any value in one expression.
{{ text | trim | upper }}
{{ items | join:',' }}
{{ value | default:'n/a' }}Loop over slices and maps with auto-bound index, first, last, and key helpers.
{{ for i, u in users }}{{ i }}: {{ u | key:'email' | lower }}
{{ endfor }}Branch with conditionals.
{{ if active }}yes{{ else }}no{{ endif }}The Template Syntax page covers all of these in full, along with whitespace control.
Include and render another template, with a recursion guard and a directory whitelist for safety.
{{ 'partial.tpl' | file | template }}Where it fits
Reach for sintax when you are generating documents, shaping data for an API call, or wiring template-driven steps into a workflow, and you want the transformation to live in the template instead of scattered across Go helpers.
Reach for html/template instead when you are rendering HTML from untrusted input.
Sintax ships manual escape modifiers (escape_html, escape_js, escape_url), but it does not auto-escape by context the way the standard library does, and that automatic, context-aware escaping is what keeps untrusted input safe.
Modifiers
Each modifier function links to its own page with full inputs, parameters, and examples.
Casing
Shift case: upper, lower, title, and slug.
lowerConverts a string to lowercase.{{ name | lower }}slugConverts a string to a URL-friendly slug.{{ title | slug }}titleConverts a hyphen-separated slug into a title-cased string.{{ slug | title }}title_modelFormats an AI model identifier into a human-readable title.{{ model | title_model }}upperConverts a string to uppercase.{{ code | upper }}Edit
Replace, wrap, shorten, and reshape strings.
concatJoins the value and every part into one string, in order, with no
separator.{{ name | concat:'-','01','.txt' }}replaceReplaces every occurrence of the old substring with the replacement in
the input.{{ text | replace:'world','everyone' }}replace_patternReplaces every match of the RE2 pattern in the input with the
replacement, which may reference capture groups with $1, $2 and so on.{{ name | replace_pattern:'(\w+), (\w+)','$2 $1' }}reverseReverses the input by rune, so multi-byte characters stay intact.{{ word | reverse }}shortenTruncates the input to at most a given number of bytes, returning it
unchanged when it is already shorter.{{ text | shorten:5 }}wrapNests the value inside a new single-entry map under the given key.{{ value | wrap:'name' }}Split & join
Split strings into parts and join them back together.
Trim
Strip whitespace, prefixes, and suffixes.
trimStrips any of the cutset characters from both ends of a string.{{ input | trim:'/' }}trim_prefixRemoves the given prefix once from the start of a string,
returning it unchanged when it does not start with that prefix.{{ path | trim_prefix:'/api' }}trim_suffixRemoves the given suffix once from the end of a string,
returning it unchanged when it does not end with that suffix.{{ file | trim_suffix:'.txt' }}Escape
Escape strings for HTML, URLs, and JavaScript.
Format
Format numbers and strings for display.
currencyConverts a numeric value between currency units by scaling it with a
ratio of unit sizes: the result is value * toUnits / fromUnits, truncated to a
whole integer.{{ price | currency:1,100 }}decimalFormats a number with the given number of decimal places,
rounding to the nearest value at that precision.{{ ratio | decimal:4 }}formatRenders a date/time value using a PHP-style date layout (Y is the
4-digit year, m the zero-padded month, d the day, H:i:s the time, and so on).{{ at | format:'Y-m-d' }}lengthReturns the number of UTF-8 bytes in a string, so a multi-byte
character such as "é" counts as more than one.{{ name | length }}line_numbersPrepends each line of the input with a line number counting up
from a given start line, so line_numbers:6 renders a block as if it began at
line six.{{ body | line_numbers:6 }}Access
Reach into arrays and maps by key or position.
findReturns the first map in a slice whose named field equals the wanted
value, scanning in order and returning the whole matching map.{{ items | find:'status','active' }}firstReturns the leading byte of a string as a one-byte string, so it
is a letter only for ASCII text; a multi-byte character yields a broken
fragment.{{ word | first }}keyReads one value out of a map by key or out of a slice by index.{{ config | key:'database.host' }}lastReturns the trailing byte of a string as a one-byte string, with
the same ASCII caveat as the first modifier.{{ word | last }}pluckReads one named field from every element of a slice of maps and returns
the collected values as a slice, in order.{{ users | pluck:'name' }}Query
Filter, test, and search collections.
filterReturns the items of a slice whose named field equals the search value.{{ items | filter:'role','admin' }}hasReports whether a collection contains something, and what "contains"
means depends on the shape of the value.{{ tags | has:'featured' }}isReports whether the value equals any one of the given candidates, a compact
way to write an "is this one of these" test in a template.{{ status | is:'active','pending' }}Transform
Sort, map, merge, and fold collections.
flattenFlattens a slice by exactly one level: any element that is itself a
slice or array is spread into the result, and every other element is copied
through unchanged.{{ groups | flatten }}mapConverts a slice of string-keyed maps into a single map keyed by the named
field's value, turning a list you have to scan into a lookup table.{{ users | map:'id' }}mergeKeys a slice of maps by the named field, producing a lookup map.{{ users | merge:'id' }}sortSorts a copy of a slice in the named direction, 'asc' or 'desc'; any
other direction is an error.{{ scores | sort:'desc' }}sumTotals the named field across a slice of maps, the way you sum one
column of a list of records.{{ items | sum:'price' }}File
Read a file's contents into a template.
Path
Pull apart file paths - directory, name, and extension.
dirnameReturns the directory portion of a file path, everything up to but not
including the final path element.{{ path | dirname }}extReturns the file extension without the leading dot, for example
"png" for "avatar.png".{{ path | ext }}ext_dotReturns the file extension including the leading dot, for
example ".png" for "avatar.png".{{ path | ext_dot }}ext_prependInserts an extra extension segment just before the existing
file extension, so "styles.css" with "min" becomes "styles.min.css".{{ path | ext_prepend:'min' }}ext_trimReturns the file path without its trailing extension.{{ path | ext_trim }}filenameReturns the base file name from a path, the final path element
including its extension.{{ path | filename }}Parse
Read JSON, YAML, and other formats into values.
from_csvParses a CSV string into a list of rows, so a serialized table (a
report export, a spreadsheet dump) becomes data that later template steps can
filter and pluck.{{ body | from_csv }}from_jsonParses a JSON object string into a map, so a serialized payload (an
API response body, a config blob) becomes data that later template steps can
index into.{{ body | from_json }}from_yamlParses a YAML document into a map, so a config file or a manifest
becomes data that later template steps can index into.{{ body | from_yaml }}Serialize
Render values as JSON, YAML, and Markdown.
jsonSerializes a value to a compact JSON string.{{ user | json }}markdownConverts an HTML string to Markdown, so a fetched page or a rich-text
field can be reduced to plain prose a later step can store or render.{{ body | markdown }}yamlSerializes a value as YAML, so a map assembled in a template can be
written out as a config file or a manifest.{{ config | yaml }}Compare
Compare values for use in conditional expressions.
eqReports numeric equality across the int and float kinds, so 5 equals
5.0.{{ total | eq:5.0 }}gtReports whether the first value exceeds the second, comparing both as
numbers so 5 and 5.0 rank the same.{{ score | gt:70 }}gteReports whether the first value is greater than or equal to the second,
comparing both as numbers so 5 and 5.0 rank the same.{{ score | gte:70 }}notInverts the truthiness of the value, following the same rules as a
template if: booleans by their value, numbers true when greater than zero,
strings true when non-empty (except the literal "false"), and collections
true when non-empty.{{ name | not }}Default
Supply a fallback when a value is missing.
Template
Render a nested template.