caddyserver/caddy · error

final element is not an array

Error message

final element is not an array

What it means

Thrown when POST targets an array destination with the '...' ellipsis form (path ends in '...') but the request body is not a JSON array. The ellipsis contract is: the parent is an array, the final path part is '...', and the payload must itself be an array whose elements are appended one by one.

Source

Thrown at admin.go:1235

							path, idxStr, err)
					}

					if idx < 0 || (method != http.MethodPut && idx >= len(arr)) || idx > len(arr) {
						return fmt.Errorf("[%s] array index out of bounds: %s", path, idxStr)
					}
				}

				switch method {
				case http.MethodGet:
					err = enc.Encode(arr[idx])
					if err != nil {
						return fmt.Errorf("encoding config: %v", err)
					}
				case http.MethodPost:
					if ellipses {
						valArray, ok := val.([]any)
						if !ok {
							return fmt.Errorf("final element is not an array")
						}
						v[part] = append(arr, valArray...)
					} else {
						v[part] = append(arr, val)
					}
				case http.MethodPut:
					// avoid creation of new slice and a second copy (see
					// https://github.com/golang/go/wiki/SliceTricks#insert)
					arr = append(arr, nil)
					copy(arr[idx+1:], arr[idx:])
					arr[idx] = val
					v[part] = arr
				case http.MethodPatch:
					arr[idx] = val
				case http.MethodDelete:
					v[part] = append(arr[:idx], arr[idx+1:]...)
				default:
					return fmt.Errorf("unrecognized method %s", method)

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Wrap the payload in a JSON array: POST '[ { "handle": ... } ]' to .../routes/...
  2. Or drop the ellipsis and POST the single object directly to .../routes to append one element

Example fix

# before
curl -X POST http://localhost:2019/config/apps/http/servers/myserver/routes/... \
  -d '{"handle":[{"handler":"static_response","body":"hi"}]}'
# after
curl -X POST http://localhost:2019/config/apps/http/servers/myserver/routes/... \
  -d '[{"match":[{"host":["a.example"]}],"handle":[{"handler":"static_response","body":"hi"}]}]'
Defensive patterns

Strategy: type-guard

Validate before calling

import json
val = json.loads(body)
assert isinstance(val, list), 'ellipsis (...) POST requires an array payload'

Type guard

func isJSONArray(body []byte) bool {
    var v any
    if json.Unmarshal(body, &v) != nil {
        return false
    }
    _, ok := v.([]any)
    return ok
}

Prevention

When it happens

Trigger: POST /config/apps/http/servers/myserver/routes/... with body '{"handle":[...]}' (an object) or '"x"' (a string). Only a JSON array payload is accepted, e.g. '[{...},{...}]'.

Common situations: Misreading the ellipsis feature and sending a single object to be wrapped; sending an array nested one level too deep or too shallow.

Related errors


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/babda5b00dd37f64. Report an issue: GitHub.