# YAML Indentation Error: Fix Mapping Values, Tabs and Kubernetes YAML

> Fix YAML indentation errors: mapping values are not allowed here, tabs vs spaces, block scalars, list indentation, anchors and Kubernetes manifests.

**Quick answer:** YAML uses spaces and relative indentation to define mappings and sequences, so one wrong column can change the document tree or stop the parser. Fix the first reported line by checking the line above it for tabs, a colon followed by a space inside an unquoted value, or a misaligned dash; then validate every document, inspect the parsed JSON, and only then run Kubernetes schema checks.

## Key takeaways

- Indentation is structure: a nested block must be more indented than its parent, while sibling keys share a column.
- Tabs are illegal in YAML indentation; replace leading tabs with spaces, but do not rewrite tabs inside block scalar content.
- The 'mapping values are not allowed here' message usually points at an unquoted colon-space or a line that was reparented by indentation.
- Block scalars and multi-document files have their own rules: content indentation, chomping indicators and a column-zero separator.
- A formatter normalizes layout but cannot fix a syntactically valid tree that Kubernetes will reject; validate syntax, structure and schema in that order.

---

YAML looks like a list of key-value lines until the parser rejects a file that appears correct. The reported line is often where the parser stopped, not where the structure broke. Most YAML indentation errors are structural rather than typographical: the line that fails is usually a symptom of a column change above it. A missing space, a tab copied from a terminal, a dash one column off, or an unquoted colon can all change the tree that the parser builds, and the same mistake produces different messages in PyYAML, Go and browser parsers.

This guide starts with the rules that make indentation structural, then works through the exact causes of "mapping values are not allowed here", block scalars, multi-document streams, anchors and Kubernetes manifests. It ends with a debugging order that separates syntax, structure and schema problems.

## How YAML indentation defines structure

In block style, YAML uses indentation instead of braces. A mapping entry has a key, a colon and either an inline scalar or a nested block. The nested block must be indented further than the key that owns it; sibling keys must start in the same column. The parser does not care that two lines look related, only whether the second line is deeper than the first.

The specification does not mandate two spaces. It requires consistency: once a block starts, its entries share an indentation level. Two spaces is the common convention, and mixing two and four spaces inside one block is a reliable way to reparent a line.

Sequences add a dash indicator. A dash may sit in the same column as the parent key or one level deeper; both forms are valid, but a file should pick one:

- `ports:` followed by `- 80` at the key's column.
- `ports:` followed by `  - 80` one level deeper.

The first form surprises readers who expect every child to be indented, while the second is easier to scan. The parser treats them the same because the dash, not the column, starts the sequence item.

Indentation is relative, not absolute. Adding a space to every line changes nothing; adding one space to a single line can move it from a mapping into a scalar, or from a list item into the parent object. That is why a one-character edit can produce an error many lines later.

A syntax-only validator such as the [YAML formatter and validator](/tools/yaml-formatter) reports the first line where the block structure stops making sense, and the browser parser also reports nested-mapping errors at the exact line.

## Why tabs break YAML indentation

YAML tabs are not a substitute for spaces: the specification forbids tab characters in indentation. A tab is a single control character with no defined display width; an editor may show it as two, four or eight columns, so the parser cannot use it as a structural column. Tabs are allowed inside quoted scalars and inside block scalar content once the required space indentation has been consumed, but a leading tab used for nesting is always invalid.

The error depends on the parser. PyYAML reports `found character '\t' that cannot start any token`; libyaml-based tools use similar wording; Go and kubectl often report `found character that cannot start any token` or `did not find expected key`; browser parsers may report a bad indentation of a mapping entry. None of these messages says "tab", which is why the problem survives a quick read.

Make tabs visible before editing. `grep -nP '\t' file.yaml` works on GNU grep, `sed -n '1,40l' file.yaml` shows tabs as `\t` on macOS and Linux, and VS Code's Render Whitespace setting draws them as arrows. An `.editorconfig` with `indent_style = space` and `indent_size = 2` stops most editors from inserting tabs again.

Do not run a blind "tabs to spaces" replacement over the whole file. A ConfigMap may store a Makefile whose recipe lines require a literal tab, and converting those tabs changes the payload. Replace leading tabs only on lines that are YAML structure, and inspect block scalar bodies separately.

## "Mapping values are not allowed here": exact causes

This wording comes from PyYAML and other libyaml-based parsers. Go-based tools often say `mapping values are not allowed in this context`, and the `yaml` npm package may say `Nested mappings are not allowed in compact mappings`. The meaning is the same: the scanner met a colon followed by whitespace while it was reading a scalar or another node that cannot accept a mapping value.

The colon in the message is usually not the key you think it is. Check these causes:

- An unquoted colon-space inside a plain value: `tier: frontend: critical`, `url: https://example.com: 8443`, `message: Release: v2`. The second `: ` is read as a mapping indicator inside the scalar.
- A mapping nested under a scalar because of indentation: `name: web` followed by ` namespace: production` one space deeper. The scalar `web` cannot own a mapping.
- A document that starts with a plain scalar and then adds a mapping: `web` on the first line and `name: web` at column zero on the next.
- A sequence item that is a scalar followed by a mapping at the wrong column: `- web` then `  name: web`.
- A missing space after a key colon: `name:web` is a plain scalar, not a key. The next indented `labels:` line then becomes a mapping under that scalar.
- A second colon in a key: `app: name: web` tries to give `app` the mapping value `name: web` on the same line.
- A Windows path written with a colon-space: `path: C: \Users\me`; `C:\Users\me` is fine because the colon is not followed by a space.

Quote the value when the colon belongs to the text, or reindent when the colon line was meant to be a sibling. The broken manifest below fails on the `tier` line:

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  labels:
    app: web
    tier: frontend: critical
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: nginx:1.27
          ports:
            - containerPort: 80
              protocol: TCP
```

PyYAML reports line 7 at the second colon. The rest of the file is structurally valid, which is why the error looks isolated: one unquoted colon is enough. The `image: nginx:1.27` line is fine because its colon is followed by `1`, not by a space.

## Sequence indentation rules that prevent errors

Sequences are where one-column mistakes become Kubernetes field errors. The dash is an indicator, not punctuation. In a sequence of mappings, the first key starts after the dash and space, and every following key in the same item must align with that first key. If `name` starts at column 10 after `- `, then `image` must also start at column 10; column 9 is outside the item, and column 11 is a deeper nested mapping.

A block sequence can sit at the parent key's column or one level deeper. `ports:` followed by a dash at the same column is valid, and `ports:` followed by a dash two spaces deeper is also valid. The parser sees the same list. Pick one style for the file; yamllint's `indentation` rule can enforce `indent-sequences: true` or `consistent`.

Nested sequences need their own dash level. A list of arguments is a sequence of scalars, while a list of containers is a sequence of mappings, and a list of ports inside each container is another sequence. Each dash must be more indented than the key that owns it, and the keys inside an item must align with each other.

The corrected manifest below fixes the colon and keeps two spaces per level. The `containers` list is indented under pod `spec`, each dash is two spaces deeper, and `name`, `image` and `ports` share a column:

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  labels:
    app: web
    tier: "frontend: critical"
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: nginx:1.27
          ports:
            - containerPort: 80
              protocol: TCP
```

If the `image` line lost two spaces, the YAML would still parse: `image` would become a field of the pod spec instead of the container, and kubectl would reject it as `unknown field "image"`. That distinction between a parse error and a schema error is the reason to inspect the parsed tree, not only the formatted text.

## Block scalars, chomping and indentation indicators

Block scalars store multi-line text. `|` is literal: every line break and every extra leading space inside the content is preserved. `>` is folded: a single line break between two lines becomes a space, a blank line becomes a newline, and more-indented lines keep their line breaks. Use `|` for scripts, configuration files and anything where newlines matter; use `>` for prose where wrapping is acceptable.

The header can carry a chomping indicator. `|` keeps one final newline, `|-` strips all trailing newlines, and `|+` keeps every trailing newline. The same suffixes work with `>`. A header comment is allowed after the indicator, as in `| # keep line breaks`.

Content must be indented more than the key or parent that owns the scalar. The indentation of the first non-empty content line sets the baseline; later non-empty lines must be at least that deep. A shallower non-empty line ends the scalar. If that line happens to be a valid sibling key, the block silently loses the rest of its text; if it is not valid YAML at that level, the parser reports an error such as `could not find expected ':'` or a mapping-values error on the text that was meant to be content.

An explicit indentation indicator is needed when the first content line starts with spaces, or when the content begins with blank lines. `|2` means the content indentation is the parent indentation plus two spaces, so extra leading spaces on the first line are preserved instead of becoming the baseline. `|2-` combines the indicator with strip chomping; the two indicators may appear in either order, so `|-2` is also valid.

Tabs inside the content are allowed once the space indentation is complete, but they are data. A make recipe stored in a ConfigMap depends on those tabs, so a formatter that converts every tab to spaces can corrupt the stored file even though the YAML still parses. The multi-document example in the next section uses `|` for an nginx configuration.

## Plain versus quoted scalars: colons, hashes and booleans

A plain scalar is unquoted text. It cannot contain a colon followed by a space, an unquoted ` #` sequence, or start with an indicator character such as `-`, `?`, `:`, `#`, `&`, `*`, `!`, `|`, `>`, `'`, `"`, `%`, `@`, backtick, `[`, `]`, `{`, `}` or `,`. It can contain a colon that is not followed by a space, which is why `nginx:1.27`, `12:30` and `https://example.com` are valid plain values.

`#` starts a comment only when it follows whitespace or begins a line. `image: nginx#1` keeps the hash as part of the value; `image: nginx #1` starts a comment. A value containing `: ` should be quoted, because the parser otherwise reads the colon as a new mapping. Single quotes keep the text literally and double a single quote to escape it; double quotes interpret `\n`, `\t` and `\uXXXX`, so use them when you need escapes, and single quotes for Windows paths and regular expressions.

Typing is the other trap. YAML 1.1 resolves `yes`, `no`, `on` and `off`, in several capitalizations, as booleans; the 1.1 schema also lists single-letter `y` and `n`, though implementations vary. It also resolves `12:30` as the base-60 integer 750 and `0777` as an octal number. YAML 1.2 core narrows booleans to `true` and `false` only, treats `yes` and `on` as strings, drops sexagesimal numbers, and uses `0o777` for octal. PyYAML and many older Kubernetes conversion paths follow 1.1; the `yaml` npm package used by the browser tools follows 1.2. The same file can therefore produce different types in different tools.

Quote anything that must remain a string: `country: "NO"`, `version: "1.20"`, `enabled: "yes"`, `time: "12:30"`. In Kubernetes, `version: 1.20` becomes the number 1.2 and can fail a string field with `cannot unmarshal number into Go struct field`; `metadata.name: yes` can become a boolean and fail with `cannot unmarshal bool`. The `on` key in a GitHub Actions workflow is the same problem at the key level.

## Multi-document files and the --- separator

A YAML stream can contain more than one document. `---` at column zero starts a new document, and `...` explicitly ends one; content after `...` needs another `---`. A document marker must not be indented. Inside a block scalar, an indented `  ---` is content, but a column-zero `---` ends the scalar and starts a new document even if you meant it as text.

Tools differ in what they accept. Python's `yaml.safe_load` reads one document and raises `expected a single document in the stream` when it finds a second; `safe_load_all` returns every document. The browser YAML formatter parses and formats all documents and rejoins them with `---`; the JSON-to-YAML converter is intentionally scoped to one document, so split a multi-document file before converting it.

A missing `---` is worse than a parse error when the parser silently overwrites duplicate keys. Two manifests with `apiVersion`, `kind`, `metadata` and `spec` at column zero become one mapping, and a lenient parser keeps the last value for each repeated key. The result can mix the first document's spec with the second document's metadata. Kubernetes paths have historically used such a parser, so always separate manifests explicitly.

Anchors and aliases are scoped to one document. An anchor defined before the first `---` cannot be referenced after it; a second document needs its own anchor definition. This is a common failure when a shared defaults block is moved to the top of a multi-document file.

The following stream contains a ConfigMap with a literal block scalar and a Deployment as a second document. The `app.conf` content is indented four spaces under its key, and the `---` is at column zero:

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: web-config
data:
  app.conf: |
    server {
      listen 80;
      location / {
        proxy_pass http://web:8080;
      }
    }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: nginx:1.27
          ports:
            - containerPort: 80
```

`kubectl apply -f multi.yaml` applies both documents. If the `---` is removed, the two objects merge and the duplicate `apiVersion` keys are resolved by the parser rather than by Kubernetes.

## Anchors, aliases and merge keys without indentation traps

An anchor starts with `&name` and attaches to the node that follows. An alias starts with `*name` and copies that node. The anchor must appear before the alias in the same document. An undefined alias fails with `found undefined alias` or `unknown anchor`, and a forward reference is not allowed.

The anchor does not change indentation. In `defaults: &defaults`, the value is the mapping that follows on the next, more-indented lines. If the child keys are written at the same column as `defaults`, they become siblings and the anchor attaches to a null value. Then `<<: *defaults` either fails as an invalid merge or becomes an ordinary `<<` key with a null value; in both cases the merge does not happen. That one-column error is easy to miss because the anchor line itself looks correct.

A merge key is written `<<: *defaults` at the same indentation as the other keys in the target mapping. It is a YAML 1.1 feature, not part of the YAML 1.2 core schema; PyYAML, js-yaml and Go's yaml packages support it, while some strict parsers treat `<<` as an ordinary key or reject it. Kubernetes converts YAML to JSON before validation, so the API server usually sees the merged fields rather than the merge key.

Merging is shallow. Keys in the mapping itself override merged keys, and for a sequence of aliases, earlier maps override later ones. A nested mapping is replaced as a whole, not deep-merged, so merging `resources` from a defaults block can discard half of the original limits and requests.

Use anchors for repetition inside one document, not across files or documents. A converter expands aliases into ordinary values, so the output grows and the shared identity disappears; a formatter may preserve the anchor syntax or expand it depending on the tool. Kubernetes manifests that rely on anchors can also be harder to review in rendered output, where the merge has already disappeared.

## Kubernetes indentation: apiVersion, kind, metadata and spec

Kubernetes YAML indentation follows the same column rules, but the manifest adds a fixed top-level shape. `apiVersion`, `kind`, `metadata` and `spec` are siblings at column zero. `metadata.name` and `metadata.labels` are two spaces under `metadata`, while `spec` returns to column zero. If `metadata` is indented under `apiVersion`, the parser either fails or turns the manifest into a different object; if `spec` is indented under `metadata`, kubectl reports an unknown field or a missing required field.

A Deployment adds two levels that are easy to confuse. `spec.template` is a pod template. `template.metadata` and `template.spec` are siblings, both under `template`. `template.spec.containers` is a list of objects; each item starts with `- name`, and `image`, `ports`, `env` and `volumeMounts` align with `name`. A list item's keys must not align with the dash, and a key one column to the left leaves the item and becomes a field of the pod spec.

The parser and the schema catch different mistakes. Misaligned `image` may still be valid YAML, but kubectl validates the resulting object and reports `unknown field "image" in io.k8s.api.core.v1.PodSpec`. A `ports` list with a second item containing only `protocol` is valid YAML, but Kubernetes reports that the item is missing `containerPort`. A Service `selector` is a flat map, while a Deployment `selector` is a `LabelSelector` with `matchLabels`, so copying one shape into the other produces an unknown-field error rather than an indentation error.

Duplicate keys are a silent Kubernetes risk. Some YAML-to-JSON paths keep the last duplicate without an error, so two `image:` lines or two `metadata.name` values can be collapsed before validation. A linter with a duplicate-key rule is the reliable check; `kubectl apply --dry-run=server` cannot recover a key that was already discarded during conversion.

YAML 1.1 typing also surfaces here. `enabled: yes` becomes a boolean and fails a string field with `cannot unmarshal bool`; `version: 1.20` becomes the number 1.2 and fails with `cannot unmarshal number`. Quote values that Kubernetes expects as strings.

The [Kubernetes YAML inspector](/tools/kubernetes-yaml) parses every document and checks `apiVersion`, `kind`, `metadata.name` and the `List` shape, which catches missing top-level fields and multi-document count problems. It does not validate the full schema, so pair it with kubectl or kubeconform.

```text
$ kubectl apply -f deployment-broken.yaml
error: error converting YAML to JSON: yaml: line 7: mapping values are not allowed in this context

$ kubectl apply -f deployment-misaligned.yaml
error: error validating "deployment-misaligned.yaml": error validating data: ValidationError(Deployment.spec.template.spec): unknown field "image" in io.k8s.api.core.v1.PodSpec; if you choose to ignore these errors, turn validation off with --validate=false
```

The first error is syntax; the second is structure. `kubectl apply --dry-run=client -f file.yaml` checks the first without a cluster, and `--dry-run=server` checks the second against the API schema. `kubectl explain deployment.spec.template.spec.containers` confirms the expected path when a field lands in the wrong object.

## Formatters, a debugging workflow and recurring mistakes

A formatter is not a fixer. It parses the document first and re-emits it with consistent indentation. If the input does not parse, the formatter reports the error; it cannot guess where the missing space or quote belongs. If the input parses but a line is in the wrong block, the formatter preserves that structure and makes it look tidy. That is why a formatted manifest can still fail kubectl validation.

Formatters can also hide evidence. Some drop comments, expand anchors and aliases, change quote style, or keep only the last duplicate key. A global tab-to-space pass can rewrite tabs inside a block scalar and change a Makefile or configuration payload. Use validate mode first, then format, and diff the output against the source before committing.

The [YAML formatter and validator](/tools/yaml-formatter) parses all documents, reports syntax errors with positions, and reformats with two-space indentation. It preserves comments where the parser supports them, and it rejects duplicate keys instead of silently choosing one. After syntax is clean, the [Kubernetes YAML inspector](/tools/kubernetes-yaml) checks the manifest shape, and the [JSON to YAML converter](/tools/json-yaml) turns a single YAML document into JSON so you can see the exact tree the parser built. Convert one document at a time and split multi-document files first.

A debugging order that works:

1. Read the first error and its line number; inspect the previous non-empty line and any colon inside an unquoted value.
2. Reveal whitespace with `grep -nP '\t'`, `sed -n '1,60l'` or the editor's whitespace display; remove leading tabs and trailing-space drift.
3. Validate syntax only with the YAML formatter or `python3 -c 'import yaml,sys; list(yaml.safe_load_all(open(sys.argv[1])))' file.yaml`; use `safe_load_all` for multi-document files.
4. Convert to JSON and inspect the nesting. If `image` is under `spec` rather than `containers[0]`, the YAML parses but the structure is wrong.
5. For Kubernetes, run `kubectl apply --dry-run=client`, then `--dry-run=server` if a cluster is available, and use `kubectl explain` or `kubeconform -strict -summary`.
6. Lint the duplicate-key, truthy and indentation rules with yamllint, and add the checks to CI so the same mistake does not return.
7. Reindent whole blocks, not single lines; a one-line fix often leaves a sibling misaligned.

The recurring mistakes are tabs, one-space drift, `key:value` without a space, unquoted `value: with: colon`, a dash misaligned with the first key of its item, block scalar content dedented to the key's column, a missing `---` between manifests, an anchor used across documents, duplicate keys, and unquoted `yes`, `on` or version numbers. Most of them are invisible in a formatter's output; all of them are visible in a parser's tree or a schema validator's error.

## Frequently asked questions

### Why does YAML reject tabs for indentation?

The YAML specification forbids tab characters in indentation because a tab has no fixed width and different editors expand it differently. Tabs are allowed inside quoted strings and block scalar content after the indentation, but a leading tab used for structure will produce a scanner error in most parsers.

### How do I fix 'mapping values are not allowed here'?

Find the colon named in the error and look at the value or the line above it. Quote any unquoted value that contains a colon followed by a space, add the missing space after a key colon, or reindent the line so a mapping is not nested under a scalar. The reported line is often where the parser gave up, not where the mistake began.

### Does a YAML formatter fix indentation errors?

A formatter can normalize indentation only after the document parses. If a line is in the wrong block but still valid YAML, the formatter preserves that wrong structure and makes it look tidy. If the input does not parse, the formatter reports an error instead of repairing it; use validation first, then formatting.

### Why does kubectl accept a duplicate key in my manifest?

Some Kubernetes YAML-to-JSON paths use a YAML 1.1 parser that keeps the last duplicate key without an error, so a repeated image or metadata.name can be silently overwritten before validation. Go yaml.v3 and many linters reject duplicates, but kubectl behavior depends on the version. Run a duplicate-key linter in CI instead of relying on kubectl.

### How should I indent a list of objects in Kubernetes YAML?

Put the dash under the list key, using either the key's column or one level deeper, and align every key in the same item with the first key after the dash. For example, name, image and ports in a container item must share a column; a key one space to the left leaves the list item and becomes a field of the parent object.

## References

- [YAML 1.2.2 Specification](https://yaml.org/spec/1.2.2/)
- [YAML 1.1 Boolean Type](https://yaml.org/type/bool.html)
- [Kubernetes Configuration Best Practices](https://kubernetes.io/docs/concepts/configuration/overview/)
- [yamllint Documentation](https://yamllint.readthedocs.io/en/stable/)

---

- Canonical: https://www.jsonmind.com/guides/yaml-indentation-error
- Locale: en
- Published: 2026-11-11
- Updated: 2026-11-11
- Tools: yaml-formatter, kubernetes-yaml, json-yaml