gohugoio/hugo · error

expected a map, a slice with an even number of elements, or

Error message

expected a map, a slice with an even number of elements, or an even number of scalar values, and each key must be a string

What it means

The `querify` function builds a URL query string from key-value pairs, encoded and sorted by key. This error covers three shapes of bad input: a single argument that is not a map, a `hmaps.Params`, a `[]string`, or a `[]any`; an odd number of scalar variadic arguments (key without value); or a `[]string`/`[]any` slice with an odd element count.

Source

Thrown at tpl/collections/querify.go:25

//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package collections

import (
	"errors"
	"net/url"

	"github.com/gohugoio/hugo/common/hmaps"
	"github.com/spf13/cast"
)

var (
	errWrongArgStructure = errors.New("expected a map, a slice with an even number of elements, or an even number of scalar values, and each key must be a string")
	errKeyIsEmptyString  = errors.New("one of the keys is an empty string")
)

// Querify returns a URL query string composed of the given key-value pairs,
// encoded and sorted by key.
func (ns *Namespace) Querify(params ...any) (string, error) {
	if len(params) == 0 {
		return "", nil
	}

	if len(params) == 1 {
		switch v := params[0].(type) {
		case map[string]any: // created with collections.Dictionary
			return mapToQueryString(v)
		case hmaps.Params: // project configuration or page parameters
			return mapToQueryString(v)
		case []string:
			return stringSliceToQueryString(v)

View on GitHub (pinned to 52c9bd7908)

Solutions

  1. Ensure scalar arguments come in key-value pairs: `{{ querify "foo" "bar" "baz" "qux" }}`.
  2. Pass a `dict` or `hmaps.Params` for a single-argument form: `{{ querify (dict "foo" "bar") }}`.
  3. If using a slice, guarantee an even number of string-castable elements.

Example fix

// before
{{ querify "q" }}
// after
{{ querify "q" "hugo" }}
// or
{{ querify (dict "q" "hugo" "page" "1") }}
Defensive patterns

Strategy: validation

Validate before calling

// Validate key-value pairing before querify
{{ $pairs := slice "k1" "v1" "k2" "v2" }}
{{ if eq (mod (len $pairs) 2) 0 }}
  {{ querify $pairs }}
{{ end }}

Type guard

func isValidQuerifyInput(params []any) bool {
    if len(params) == 0 { return true }
    if len(params) == 1 {
        switch params[0].(type) {
        case map[string]any, hmaps.Params, []string, []any: return true
        default: return false
        }
    }
    return len(params)%2 == 0
}

Prevention

When it happens

Trigger: Passing an odd number of scalar args (`{{ querify "foo" }}`), passing a single unsupported type (`{{ querify 42 }}`), or passing a slice with an odd length (`{{ querify (slice "a" "1" "b") }}`). The checks fire at querify.go:51 (unsupported single type), querify.go:56 (odd scalar count), and querify.go:96 (odd slice length).

Common situations: A developer forgets the value half of a key-value pair, or passes a struct/number where a map or string pairs are expected. Also seen when front matter values are conditionally included, leaving a dangling key after a `del` or absent value.

Related errors


AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09). Data as JSON: /api/errors/3b5dcc3390322731. Report an issue: GitHub.