Skip to content

Template

Render Go templates with structured data. The template executor processes a script body as a Go text/template and writes the result to stdout or a file. It is useful for generating configuration files, reports, or any text output from structured data without spawning a shell.

How It Works

  1. The script field is parsed as a Go template.
  2. Data from with.data is passed to the template as the root context (.).
  3. The rendered output is written to stdout (capturable with output:), or to a file if with.output is set.

Dagu skips all variable expansion (${VAR}, command substitution, etc.) on the script body. The template engine is the sole evaluator — ${VAR} literals in your template are preserved as-is. Data values in with.data are expanded by Dagu before being passed to the template, so ${VAR} in data values works normally.

with Fields

FieldTypeRequiredDescription
dataobjectNoKey-value pairs accessible as {{ .key }} in the template. Values can be strings, numbers, lists, or nested objects.
outputstringNoFile path to write the rendered output to. If empty, output goes to stdout. Relative paths resolve against the step's working_dir.

Basic Example

yaml
steps:
  - name: render
    type: template
    with:
      data:
        greeting: hello
    script: |
      {{ .greeting }}, world!
    output: RESULT

RESULT captures hello, world!.

Writing to a File

yaml
steps:
  - name: render
    type: template
    with:
      output: /tmp/report.md
      data:
        title: Monthly Report
    script: |
      # {{ .title }}
      Generated by Dagu.

When with.output is set, the rendered content is written atomically to that path. Parent directories are created automatically. Stdout remains empty.

Relative paths resolve against working_dir:

yaml
steps:
  - name: render
    type: template
    working_dir: /opt/reports
    with:
      output: subdir/output.txt
      data:
        msg: hello
    script: "{{ .msg }}"

This writes to /opt/reports/subdir/output.txt.

Using Data from Prior Steps

Data values are expanded by Dagu before the template runs, so captured output variables work:

yaml
type: graph
steps:
  - id: producer
    command: 'echo -n "Alice"'
    output: NAME

  - id: render
    depends:
      - producer
    type: template
    with:
      data:
        name: ${NAME}
    script: "Hello, {{ .name }}!"
    output: RESULT

RESULT captures Hello, Alice!.

Template Functions

The template executor provides Dagu-specific functions plus functions from slim-sprig's hermetic text-template function map. Dagu removes functions for environment access, network lookup, current time, random generation, and crypto key generation. The Dagu-specific functions use pipeline-compatible argument order, where the pipeline value is the last argument.

Dagu-specific functions

These override or extend slim-sprig with pipeline-friendly argument order:

FunctionSignatureDescription
splitsplit sep sSplit string s by separator sep. Returns []string.
joinjoin sep listJoin a list with separator sep. Accepts []string, []any, or any slice.
countcount vLength of a slice, map, array, or string.
addadd b aInteger addition: a + b. Pipeline: {{ 5 | add 3 }}8.
emptyempty vReturns true if the value is nil, empty string, or empty collection.
upperupper sUppercase string.
lowerlower sLowercase string.
trimtrim sTrim whitespace from both ends.
defaultdefault def valReturns def if val is empty/nil/zero; otherwise returns val.

Available slim-sprig functions

These non-overridden names come directly from slim-sprig and work as documented in the slim-sprig docs:

  • Misc: hello
  • Strings: adler32sum, cat, contains, hasPrefix, hasSuffix, indent, nindent, plural, quote, repeat, replace, sha1sum, sha256sum, splitList, splitn, squote, substr, title, toString, toStrings, trimAll, trimPrefix, trimSuffix, trimall, and trunc
  • Numeric and conversion: add1, atoi, biggest, ceil, div, float64, floor, int, int64, max, maxf, min, minf, mod, mul, round, seq, sub, toDecimal, until, and untilStep
  • Defaults and JSON: all, any, coalesce, compact, fromJson, mustCompact, mustFromJson, mustToJson, mustToPrettyJson, mustToRawJson, ternary, toJson, toPrettyJson, and toRawJson
  • Reflection: deepEqual, kindIs, kindOf, typeIs, typeIsLike, and typeOf
  • Paths and file paths: base, clean, dir, ext, isAbs, osBase, osClean, osDir, osExt, and osIsAbs
  • Encoding: b32dec, b32enc, b64dec, and b64enc
  • Collections and dictionaries: append, chunk, concat, dict, dig, first, get, has, hasKey, initial, keys, last, list, mustAppend, mustChunk, mustFirst, mustHas, mustInitial, mustLast, mustPrepend, mustPush, mustRest, mustReverse, mustSlice, mustUniq, mustWithout, omit, pick, pluck, prepend, push, rest, reverse, set, slice, sortAlpha, tuple, uniq, unset, values, and without
  • Flow control: fail
  • Regex: mustRegexFind, mustRegexFindAll, mustRegexMatch, mustRegexReplaceAll, mustRegexReplaceAllLiteral, mustRegexSplit, regexFind, regexFindAll, regexMatch, regexQuoteMeta, regexReplaceAll, regexReplaceAllLiteral, and regexSplit
  • URLs: urlJoin and urlParse

The names split, join, add, empty, lower, upper, trim, and default are available, but Dagu overrides the slim-sprig implementation with the behavior documented in the Dagu-specific table above.

Blocked functions

These function names are not available and will cause a template parse error if used:

  • Environment access: env, expandenv
  • Network I/O: getHostByName
  • Current time and date helpers: ago, date, dateInZone, dateModify, date_in_zone, date_modify, duration, durationRound, htmlDate, htmlDateInZone, mustDateModify, mustToDate, must_date_modify, now, toDate, and unixEpoch
  • Crypto key generation: buildCustomCert, derivePassword, genCA, genPrivateKey, genSelfSignedCert, and genSignedCert
  • Random generation: randAlpha, randAlphaNum, randAscii, randBytes, randInt, randNumeric, randString, and uuidv4

Missing Key Behavior

Templates use missingkey=error. Referencing a key not present in data causes the step to fail:

yaml
steps:
  - name: render
    type: template
    with:
      data:
        name: test
    script: "{{ .undefined_key }}"  # Fails with execution error

Use default to handle optional keys safely:

yaml
script: '{{ .name | default "Anonymous" }}'

Or use get for safe map access:

yaml
script: '{{ get .app "owner" | default "unknown" }}'

Complex Example

yaml
steps:
  - name: render-config
    type: template
    script: |
      app={{ .app.name | lower | replace " " "-" }}
      owner={{ get .app "owner" | default "unknown" }}
      domains={{ get .app "domains" | default (list "localhost") | uniq | sortAlpha | join "," }}
    with:
      data:
        app:
          name: My Service
          domains:
            - api.example.com
            - api.example.com
            - app.example.com
    output: RESULT

Output:

app=my-service
owner=unknown
domains=api.example.com,app.example.com

Dollar Sign Preservation

Because Dagu skips expansion on the script body, shell-style variables like ${BAR} and backtick expressions pass through unchanged:

yaml
steps:
  - name: render
    type: template
    with:
      data:
        name: test
    script: |
      export FOO=${BAR}
      echo "{{ .name }}"
      value=`command`
    output: RESULT

The output contains literal ${BAR} and `command`.

Pipeline Chaining

Functions compose naturally in pipelines:

yaml
script: '{{ "a,b,c" | split "," | join ";" }}'
# Result: a;b;c
yaml
script: '{{ .csv | split "," | count }}'
# With csv: "x,y,z" → 3
yaml
script: '{{ .domains | uniq | sortAlpha | join "," }}'
# slim-sprig list functions return []any; join accepts both []string and []any

Released under the MIT License.