Introduction#

helm-schema generates a JSON Schema for a Helm chart’s values.yaml by statically analyzing the chart’s templates. You point it at a chart directory (or a packaged .tgz), and it prints a Draft-07 schema:

helm-schema ./mychart --output mychart/values.schema.json

Helm reads a values.schema.json placed next to values.yaml automatically and validates user-supplied values against it during install, upgrade, lint, and template. A good schema turns a class of “it rendered, but wrong” mistakes into an error at submit time.

The problem with reading values.yaml#

The obvious way to describe a chart’s values is to look at its values.yaml. Most schema generators do exactly that — they read the defaults, and optionally some annotations in comments, and emit types to match.

But values.yaml is a set of defaults, not a contract. It tells you what a value is set to, never what it is allowed to be:

service:
  port: 80          # is this an int? a string? does anything else validate?
replicas: 1         # must it be ≥ 0? an int32? could a quoted "1" work?
ingress:
  enabled: false    # a bool — or is it used as a truthy guard that accepts anything?

The defaults file can’t answer those questions, because the answer lives in the templates. Reading values.yaml alone also can’t see values that have no default at all but are still consumed by a template, and it can’t distinguish a value that must be an object from one that merely happens to be an empty map today.

The approach#

helm-schema recovers the contract from the two places that actually define it:

  1. How the templates consume each value — the .Values.* paths that appear in render logic, and the control flow (if, with, range, default, eq, not, or) that guards them.
  2. What Kubernetes expects at the target field — once a value is traced to a resource field (say a Deployment’s spec.replicas), its type comes from the upstream Kubernetes or CRD schema for that field.
templates/deployment.yamlchart
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: {{ .Values.replicaCount }}
  revisionHistoryLimit: {{ .Values.revisionHistoryLimit }}
values.schema.jsongenerated
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "properties": {
    "replicaCount": {
      "anyOf": [
        {
          "description": "Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1.",
          "format": "int32",
          "type": "integer"
        },
        {
          "pattern": "^(([+-]_*)?(0|[1-9][0-9_]{0,17}|0[xX][0-9a-fA-F]{1,15}|0[bB][01]{1,62}|0[oO][0-7]{1,20}|0[0-7]{1,20})|[+-]?0[0-7]{0,8}[89][0-9]{0,8})$",
          "type": "string"
        }
      ]
    },
    "revisionHistoryLimit": {
      "anyOf": [
        {
          "description": "The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.",
          "format": "int32",
          "type": "integer"
        },
        {
          "pattern": "^(([+-]_*)?(0|[1-9][0-9_]{0,17}|0[xX][0-9a-fA-F]{1,15}|0[bB][01]{1,62}|0[oO][0-7]{1,20}|0[0-7]{1,20})|[+-]?0[0-7]{0,8}[89][0-9]{0,8})$",
          "type": "string"
        }
      ]
    }
  },
  "type": "object",
  "x-helm-schema-generated": true
}

Neither replicaCount nor revisionHistoryLimit has a stated type anywhere in the chart. helm-schema types both as int32 — and carries over Kubernetes’ own field descriptions — purely from where they land in the Deployment.

What makes it different#

Reads values.yamlhelm-schema
Source of truththe defaults filethe templates
Sees values with no defaultnoyes
Understands guards (if/with/range/…)noyes
Types from the Kubernetes/CRD APInoyes
Preserves genuine ambiguitycollapses to the default’s typekeeps a union, or abstains

And it doesn’t guess. When a value is genuinely ambiguous, the schema keeps it permissive (a union, or an explicit “unknown”) rather than collapsing it to a convenient type that might reject valid input. You get a schema that reflects what your chart really accepts — not a plausible-looking one that’s subtly wrong.

What you can do with it#

GoalWhere
Generate a schema for a chartQuick start
Understand the pipelineHow it works
Tune template analysis and required fieldsTemplate analysis, Values & defaults
Control Kubernetes / CRD schema lookupKubernetes schemas, CRD schemas
Fill inference gaps by handSchema overrides
Wire it into CIContinuous integration

helm-schema is alpha. It works well for many charts, but Helm templating has many edge cases (whitespace trimming, dynamic keys, helper indirection, runtime-only behavior). Expect some charts to need overrides or to surface an occasional incorrect or missing inference. See the FAQ.