skip to content

In a Helm chart template, what do the `{{-` and `-}}` trim markers do to the rendered YAML?

level: juniorimportance: must knowfreq 76%

answer

  1. The dash faces what it deletes
  2. A control-flow line still emits bytes
  3. Whitespace includes the newline
  4. The right-hand marker joins lines
  5. The dash needs a space after it

basics

~20 s

A leading {{- deletes every whitespace character before the action, including the newline that ended the previous line, and a trailing -}} deletes the whitespace after it, including its own newline. Chart authors use them so control-flow lines leave no blank lines in the rendered YAML.

solid answer

~50 s

Helm renders `templates/` with Go's template engine, which copies every byte that is not part of an action straight to the output — including the spaces in front of `{{` and the newline that ends the line. A line holding only `{{ if .Values.metrics.enabled }}` therefore still emits a blank line into the manifest. The trim markers delete that whitespace: `{{-` walks backwards through spaces, tabs and newlines until it hits non-whitespace, and `-}}` does the same forwards. Because a chart's output is YAML, where layout is syntax, the convention is to write `{{- if ... }}`, `{{- range ... }}` and `{{- end }}` at the start of their own line so those lines vanish from the rendered document. The right-hand marker is used far more rarely: `-}}` swallows the newline that terminates the current line, so the next line's content is pulled up onto it. The dash must be followed by a space to be a marker at all.

code

yaml · 8 lines
yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: tiles-config
data:
  {{- range $key, $value := .Values.tileServer.settings }}
  {{ $key }}: {{ $value | quote }}
  {{- end }}

go deeper

for a junior

Be ready to point at either dash and say which side's whitespace it deletes, and to write a {{- if ... }} / {{- end }} pair from memory without leaving a blank line in the output.

for a middle

An interviewer expects you to explain that the engine copies every byte outside an action, that the trim is greedy across newlines, and why the trailing marker is the one that actually corrupts a document.

for a senior

Show how you spot an over-chomped line while reading a rendered manifest, and how you keep one trimming convention across a chart so that renders diff cleanly between environments.

for a principal

Own the convention rather than the keystroke: decide whether the team optimises templates for readability or rendered output for reviewability, and whether rendered manifests are checked in CI at all.

## What the engine actually emits Helm renders every file under `templates/` through Go's template engine and then parses the result as a YAML document. The engine is a text substitution machine: everything outside an action — everything that is not between `{{` and `}}` — is copied to the output byte for byte. That includes the spaces indenting an action and the newline that terminates the line the action sits on. This is the whole reason trim markers exist. A template line that reads two spaces, then `{{ if .Values.metrics.enabled }}`, then a line break, contributes two spaces and a line break to the rendered manifest even though the action itself produces no text. Control-flow lines are invisible in the template but very much present in the output. ## What the dashes do Writing the action as `{{- if .Values.metrics.enabled }}` makes the engine walk backwards from the opening brace and delete every whitespace character it finds — space, tab, carriage return, newline — until it reaches a character that is not whitespace. Writing `-}}` is the mirror image: the engine walks forwards from the closing brace and deletes whitespace the same way. The dash faces the whitespace it removes. Two details matter in practice. The trim is greedy across line boundaries, so a `{{-` will eat several blank lines above it, not just the one immediately before. And the dash must be followed by a space: `{{- 3 }}` is a trim marker followed by the number three, while `{{-3}}` is just the number minus three, and `{{-if ...}}` is not a marker either — the engine reads the dash as part of the expression and the template fails to parse. ## Why YAML makes it matter In a language where layout is syntax, the stakes look higher than they are, so it is worth separating the cosmetic case from the fatal one. A forgotten `{{-` normally leaves a blank line or a line of trailing spaces inside a mapping or a sequence. YAML tolerates those, so the usual cost is an ugly manifest, a noisy diff between two renders, and a document that is harder to read when you are counting lines to find a parse error. It is a style defect much more often than a break. The dangerous marker is the trailing one. `-}}` deletes the newline that ends the current line, which means the next line's content is appended to it. A template that ends a line with `name: {{ .Release.Name -}}` renders that release name and then immediately the following line's first non-whitespace characters, producing something like `name: tileslabels:`. That is a corrupt document, and the parse error it raises points at a line you did not write. This is why charts use the leading marker liberally and the trailing one almost never. One more consequence is worth knowing: because the markers only delete whitespace, a template file whose entire body is wrapped in a conditional renders to nothing but whitespace when the condition is false, and Helm produces no object from it. That is the mechanism behind the `enabled` flag pattern found in nearly every chart. ## What the markers do not do Trim markers never add whitespace and never change the indentation of a value the action emits. If an action splices in a multi-line value, the second and subsequent lines arrive at whatever indentation the value already carries — usually none. Fixing that is a separate job, done with `indent` and `nindent`, and confusing the two produces a chart where someone keeps adding dashes to a line whose real problem is depth. ## A worked shape The canonical layout puts the control-flow actions at the start of the line with a leading dash, and leaves the content lines alone: ```yaml data: {{- range $key, $value := .Values.tileServer.settings }} {{ $key }}: {{ $value | quote }} {{- end }} ``` The `range` and `end` lines disappear entirely; the content line keeps its two literal spaces because those spaces are the YAML indentation of each emitted key. If you also dashed the content line's action, you would delete the indentation you need, and the keys would land at column zero. ## Reading a chart When you inspect an unfamiliar chart, the trim markers tell you which lines the author considered structural. A dashless `{{ if }}` in the middle of a well-maintained chart is usually a small oversight; a `-}}` at the end of a line is either a deliberate joining trick or a bug, and it is worth rendering that template to find out which.

  • How far back does `{{-` trim in a Helm template?
    It is greedy. The engine deletes spaces, tabs, carriage returns and newlines backwards until it reaches a character that is not whitespace, so several blank lines above the action collapse to nothing. It stops at the first real text and never removes characters from a line that has content on it.
  • If a Helm template file renders to nothing but whitespace, what object does it produce?
    None. Helm drops rendered manifests that are empty or whitespace-only, which is exactly why the `{{- if .Values.something.enabled }}` wrapper around a whole file is a safe way to make an object optional. The file stays in the chart and simply contributes nothing to the release.
  • Do the trim markers change the indentation of a multi-line value an action emits?
    No. They only delete whitespace adjacent to the action itself; they never insert any. A spliced multi-line value arrives at whatever indentation it already carries, which for a value converted from `.Values` is normally none. Adding the required depth is the job of `indent` or `nindent`.

Think of each dash as an eraser that sweeps in the direction it points: the left dash sweeps backwards over the whitespace behind the action, the right dash sweeps forwards over the whitespace ahead of it, and both keep going until they hit real text.

saying these in an interview costs you the question

  • Calls the dashes YAML syntax rather than template trim markers
  • Thinks {{- removes spaces but keeps the newline
  • Believes a line holding only {{ if }} vanishes on its own
  • Says the markers set the indentation of a spliced value
  • Writes {{-if}} with no space after the dash
  • Adds -}} at line ends and cannot explain the joined lines

context