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 config.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 config.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 config.data are expanded by Dagu before being passed to the template, so ${VAR} in data values works normally.

Config 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
    config:
      data:
        greeting: hello
    script: |
      {{ .greeting }}, world!
    output: RESULT

RESULT captures hello, world!.

Writing to a File

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

When config.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
    config:
      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
    config:
      data:
        name: ${NAME}
    script: "Hello, {{ .name }}!"
    output: RESULT

RESULT captures Hello, Alice!.

Template Functions

The template executor provides functions from slim-sprig (the hermetic subset — no env, network, or random access) plus Dagu-specific overrides. All functions use pipeline-compatible argument order (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.

Selected slim-sprig functions

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

FunctionExample
replace{{ "hello world" | replace "world" "dagu" }}hello dagu
contains{{ contains "ell" "hello" }}true
hasPrefix / hasSuffix{{ hasPrefix "hel" "hello" }}true
list{{ list "a" "b" "c" }} → creates a list
uniq{{ .items | uniq }} → deduplicate
sortAlpha{{ .items | sortAlpha }} → sort strings
dict{{ $d := dict "key" "value" }} → create a map
get{{ get .map "key" }} → safe map access (returns "" if missing)
dig{{ dig "a" "b" "fallback" .data }} → nested map access
has{{ list "a" "b" | has "a" }}true
toJson / toPrettyJsonSerialize to JSON
regexMatch / regexFindRegex operations
toString{{ 42 | toString }}"42"
substr{{ substr 0 5 "hello world" }}hello

Blocked functions

These functions are removed for safety — they will cause a template parse error if used:

  • Environment access: env, expandenv
  • Network I/O: getHostByName
  • Non-deterministic time: now, date, dateInZone, ago, duration, unixEpoch, etc.
  • Crypto key generation: genPrivateKey, derivePassword, genCA, genSelfSignedCert, genSignedCert, buildCustomCert
  • Random generation: randBytes, randString, randNumeric, randAlphaNum, randAlpha, randAscii, randInt, 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
    config:
      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 "," }}
    config:
      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
    config:
      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.