Helm’s --set-json flag lets you override values in your values.yaml file using JSON, but its true power emerges when you need to target deeply nested structures or arrays.

Let’s see it in action. Imagine you have a values.yaml like this:

replicaCount: 1
image:
  repository: nginx
  tag: ""
  pullPolicy: IfNotPresent
service:
  type: ClusterIP
  port: 80
  annotations:
    "nginx.org/websocket-services": "true"
    "prometheus.io/scrape": "true"
    "prometheus.io/port": "9102"
resources: {}
podAnnotations: {}
nodeSelector: {}
tolerations: []
affinity: {}

And you want to change the image.tag to 1.21.0 and add a new annotation monitoring.io/enabled: "true" to service.annotations.

Normally, you’d do:

helm install my-release my-chart -f values.yaml \
  --set image.tag=1.21.0 \
  --set service.annotations."nginx\.org/websocket-services"=false \
  --set service.annotations.monitoring\.io/enabled="true"

Notice how you have to escape dots in keys and use quotes for keys with special characters. It gets messy fast with deep nesting.

Now, with --set-json, you can achieve the same, but with more flexibility, especially for complex structures.

To set the image.tag:

helm install my-release my-chart -f values.yaml \
  --set-json "image.tag" '"1.21.0"'

Here, "image.tag" is the path to the value, and '"1.21.0"' is the JSON representation of the string value, which Helm then parses. The outer quotes are for the shell, and the inner quotes are for the JSON string.

To add or modify multiple annotations under service.annotations, you can pass a JSON object:

helm install my-release my-chart -f values.yaml \
  --set-json "service.annotations" '{"nginx.org/websocket-services": "false", "prometheus.io/scrape": "true", "prometheus.io/port": "9102", "monitoring.io/enabled": "true"}'

This replaces the entire service.annotations map with the new JSON object. If you only wanted to add or update specific keys without replacing the whole map, --set-json alone isn’t the direct answer; you’d typically prepare a JSON file containing the entire map you want to end up with.

The real magic happens with arrays. Suppose your tolerations looked like this:

tolerations:
  - key: "key1"
    operator: "Equal"
    value: "value1"
    effect: "NoSchedule"

And you want to add another toleration. You can’t just --set tolerations[1] because Helm expects a valid JSON array. You’d construct a JSON array for the entire tolerations field:

helm install my-release my-chart -f values.yaml \
  --set-json "tolerations" '[{"key": "key1", "operator": "Equal", "value": "value1", "effect": "NoSchedule"}, {"key": "node-role.kubernetes.io/master", "operator": "Exists", "effect": "NoSchedule"}]'

This command completely replaces the tolerations array with the new one.

The --set-json flag is powerful because it bypasses some of the shell’s and Helm’s parsing complexities when dealing with nested structures and JSON-specific types (like booleans, numbers, and nulls) that can be tricky with --set. For example, setting a boolean true with --set would require --set myBool=true, but Helm might interpret true as a string. With --set-json, you’d use '"true"' (shell quotes around JSON string true) or even just true if the shell doesn’t interfere.

When you use --set-json, Helm treats the value as a JSON string and parses it directly. This means that JSON syntax rules apply. For JSON strings, you need to ensure they are correctly quoted within the JSON structure itself. For example, to set a string value "my-string", the command would look like --set-json "path.to.value" '"my-string"'. The outer quotes are for the shell to pass the argument, and the inner quotes are part of the JSON string value. If you wanted to set a JSON boolean true, you’d use --set-json "path.to.value" 'true'.

The most surprising thing about --set-json is how it handles escaping. While standard --set requires escaping dots in keys and special characters, --set-json expects valid JSON paths. If your JSON key itself contains a dot or special character that would be interpreted by a JSON parser, you need to represent that within the JSON string itself, not by escaping it for the shell or Helm’s --set logic. For example, if you had a value like {"my.key": "my.value"}, you’d set it using --set-json "parent.object" '{"my.key": "my.value"}'. The dots within "my.key" are treated as literal characters within the JSON object’s key.

The next hurdle you’ll face is understanding how --set-json interacts with helm template and how to manage these overrides in CI/CD pipelines.

Want structured learning?

Take the full Helm course →