skip to content

Template Functions

The functions a chart may call: sprig v3.3.0 plus Helm's own default, required, toYaml and b64enc, with env and expandenv deliberately removed. required versus default is the real question.

part ofHelmoverview, primer and where to startread it →
on this pageshow

questions

4

In a Helm chart template, how do the `default` and `required` functions differ?

level: juniorimportance: must knowfreq 76%

answer

  1. One continues the render, one stops it
  2. Fallback is the first argument, value arrives by pipe
  3. Sprig's idea of empty is wider than nil
  4. Zero and false are not the same to both
  5. Failure happens before any revision is recorded

basics

~20 s

Helm's default function substitutes a fallback when a values entry is empty, so the render continues. required does the opposite: it aborts the whole render with the message you wrote. Use default for optional settings, required where no safe fallback exists.

solid answer

~50 s

Both decide what happens when a values entry is missing, and they are opposites. `{{ .Values.migration.backoffLimit | default 7 }}` returns 7 whenever the entry is *empty* — and sprig's idea of empty is broad: `nil`, `""`, `0`, `false` and empty collections all qualify, which is why `replicaCount: 0` silently renders 3 under a `default 3`. `{{ required "set migration.image" .Values.migration.image }}` returns the value untouched when it is set and fails the render with that message when it is nil or an empty string; a `false` or a `0` passes it. Because both run at render time, `helm template` and `helm lint` reproduce a failing `required` before anything reaches the cluster and before any release revision exists. Applying both to the same key is pointless — the default guarantees the required can never fire.

code

yaml · 12 lines
yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ .Release.Name }}-migrate
spec:
  backoffLimit: {{ .Values.migration.backoffLimit | default 7 }}
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: {{ required "migration.image is required: set it to the image holding the migration binary" .Values.migration.image | quote }}

go deeper

for a junior

Be ready to write both from memory: the fallback is the first argument to default so it pipes, and required takes a message then the value. Knowing which one stops the render is the whole first answer.

for a middle

Explain the mechanics: sprig's emptiness test covers nil, empty string, 0, false and empty collections, while required only rejects nil and the empty string. Show the zero-and-false trap and the fix of declaring fallbacks in values.yaml.

for a senior

Show judgement about where failures land. A required fails the render, so helm template in CI catches it before any revision exists; discuss which keys deserve required at all, and how the message doubles as the chart's error documentation.

for a principal

Own the values contract: which entries the chart guarantees a working default for, which it refuses to guess, and how that decision is documented for consumers who never read your templates. Stacked defaults and hidden template-only fallbacks are the debt you are preventing.

### Two functions, opposite jobs A Helm chart is a set of Go templates rendered against a merged values tree, and every template eventually asks the same question: what do I do when the entry I need is not there? `default` and `required` are the two answers. `default` is a sprig function; `required` is one of the small set Helm adds on top of sprig. One keeps the render going with a fallback, the other stops it with a message you wrote. ### `default` It is written `{{ default 7 .Values.migration.backoffLimit }}` or, far more commonly, with the value arriving through a pipe: `{{ .Values.migration.backoffLimit | default 7 }}`. The fallback is the *first* argument precisely so the pipeline form reads naturally. The rule is that `default` returns the fallback when the value is **empty**, and sprig's notion of empty is wider than most people expect. All of these are empty: - `nil` (the key is absent entirely) - the empty string `""` - the number `0` - the boolean `false` - an empty list `[]` or empty map `{}` That breadth is the single most common bug written with this function. Take a batch-job chart that installs a database migration for a chat fan-out service. The template says `terminationGracePeriodSeconds: {{ .Values.migration.gracePeriod | default 45 }}`. An operator who wants the migration pod killed immediately sets `gracePeriod: 0` — and gets 45. The same trap bites booleans: `{{ .Values.metrics.enabled | default true }}` re-enables the thing the user just turned off, because `false` is empty. The fix is not a cleverer template. It is to put the fallback in `values.yaml`, where a consumer can read it, and template the value directly: `values.yaml` carries `gracePeriod: 45`, the template carries `{{ .Values.migration.gracePeriod }}`, and value merging does the work. Where you genuinely need an in-template presence test rather than an emptiness test, ask about presence explicitly — `hasKey .Values.migration "gracePeriod"`, or `kindIs "invalid"` to detect `nil`. Sprig's `coalesce` is the multi-argument cousin: it returns the first non-empty of several candidates, and inherits exactly the same definition of empty. ### `required` Written `{{ required "migration.image must be set to the fan-out service image" .Values.migration.image }}`. When the value is set, `required` returns it unchanged, so it composes in a pipeline: `{{ required "..." .Values.migration.image | quote }}`. When it is not, the render aborts and your message is what the operator sees. Its emptiness test is deliberately narrower than `default`'s: it rejects `nil`, and for strings it rejects `""`. A boolean `false` and a numeric `0` satisfy `required`. That asymmetry is worth remembering, because it means the two functions do not agree on what "missing" means, and a question about it separates people who have read the behaviour from people who have assumed it. Two consequences matter operationally. First, the failure is a **render** failure: `helm template`, `helm install --dry-run=client` and `helm lint` all reproduce it locally. Nothing is submitted to the cluster, no objects are applied and no release revision is recorded — the pipeline stops at the render stage. Second, `required` is only evaluated if the branch containing it is rendered. Wrapping it in `{{ if .Values.migration.enabled }}` makes the requirement conditional: the DSN is mandatory only when the pre-upgrade migration hook is actually part of the release. ### Writing them well The message is the entire user interface of `required`, so name the key path and say what to set: `"migration.image is required: set it to the image containing the migration binary"` beats `"image required"`. Do not stack them. `{{ required "..." (.Values.x | default "y") }}` can never fail, because the default has already made the value non-empty — it reads as a safety belt and is dead code. Do not hide defaults in templates. A `values.yaml` that omits a key while the template silently supplies `default 7` means a consumer reading the chart's values file cannot see the effective configuration, and `helm show values` — which prints `values.yaml` — tells them nothing about it. Declare the key with its fallback in `values.yaml`, and reserve `required` for the entries that genuinely have no sensible fallback: an image reference, a hostname, a connection string. Everything else should have a working default so that `helm install` with no flags produces something that runs.

  • A user sets `replicaCount: 0` and the chart still renders 3 replicas. What happened, and how do you fix it?
    The template pipes the value through `default 3`, and sprig's `default` treats `0` as empty, so the fallback wins. The fix is to declare `replicaCount: 3` in `values.yaml` and template `{{ .Values.replicaCount }}` directly, so value merging supplies the fallback and an explicit `0` survives. If you must decide in the template, test presence with `hasKey` rather than emptiness.
  • Does `required` fail when the value is `false`?
    No. `required` rejects `nil` and, for strings, the empty string; a boolean `false` or a numeric `0` passes straight through. So you cannot use `required` to insist that someone explicitly chose a boolean. If that is genuinely needed, check for the key's presence instead — `hasKey .Values "tls"` — and call `fail` with your own message when it is absent.
  • Where does a failing `required` surface, and what state does it leave behind?
    At render time, so `helm template`, `helm install --dry-run=client` and `helm lint` all reproduce it on a laptop or in CI. Because the render never completes, no manifest is built, nothing is submitted to the API server, and no release revision is created — an existing release is untouched. That makes `required` a cheap gate: the pipeline fails before it can produce a half-applied upgrade.

default is a spare tyre in the boot; required is a warning light that refuses to let the car start. One lets the journey continue quietly, the other makes you fix the thing before you move.

saying these in an interview costs you the question

  • Claiming default only fires when the key is absent
  • Saying required also rejects false and 0
  • Thinking a failing required creates a failed release revision
  • Wrapping a default inside required as extra safety
  • Keeping fallbacks only in templates, never in values.yaml
  • Believing required is a sprig function like default

context

open as a page

Why does a Helm chart fail to render with `function "env" not defined` when sprig documents an env function?

level: middleimportance: should knowfreq 41%

basics

~20 s

Helm registers the sprig function set but deliberately deletes env and expandenv, and Go templates reject unknown function names while parsing. A chart therefore cannot read the environment of whoever runs helm; caller-side data must arrive as values.

open as a page

In a Helm chart template, what do `toYaml`, `fromYaml` and `toJson` do?

level: middleimportance: should knowfreq 58%

basics

~20 s

They convert between structured values and text inside a Helm template. toYaml serialises any values subtree to a YAML string the caller must indent; fromYaml parses a YAML string back into a map; toJson emits compact JSON, which is also valid YAML.

open as a page

In a Helm chart, why does `getHostByName` render an empty string?

level: seniorimportance: nice to knowfreq 14%

basics

~20 s

Helm keeps DNS resolution switched off during rendering, so sprig's getHostByName returns an empty string unless the rendering command is given --enable-dns. Even then the lookup runs on the machine running helm, not inside the cluster.

open as a page