binder / Docs / Options

Options

Two options ride on the tag, and two more are passed per call. Between them they cover the questions binding has to answer: what happens when a value is missing, when it is empty, when the body is enormous, and when the body carries keys the struct never asked for.

Tag options

An option follows the key, comma-separated, in whichever tag binder reads for that field:

type Request struct {
	Email string `body:"email,required"`
	Nick  string `body:"nick,omitempty"`
}

required

Binding fails if the value is missing from its source. The error is a *BindError naming the field, wrapping ErrMissingRequired:

if errors.Is(err, binder.ErrMissingRequired) {
	http.Error(w, err.Error(), http.StatusBadRequest)
	return
}

What counts as missing depends on the source, because the sources differ:

SourceMissing when
path, query, headerAbsent or empty — neither source distinguishes the two, so ?q= is treated as no q.
body, jsonThe key is absent. A key present with an empty value satisfies required.
cookieNo cookie of that name was sent. A cookie with an empty value satisfies required.

Binding stops at the first missing required field. If you need every problem in one response rather than the first, collect them in a Validate method instead — binder deliberately does not accumulate.

omitempty

Skip binding when the value is present but empty, leaving the field at whatever it already held. That is what makes defaults work: set the field before binding, and an empty input will not overwrite it.

type ListOptions struct {
	PerPage int    `query:"per_page,omitempty"`
	Sort    string `query:"sort,omitempty"`
}

opts := ListOptions{PerPage: 25, Sort: "created"}
if err := binder.Bind(r, &opts); err != nil {
	// ...
}
// ?per_page= leaves PerPage at 25 rather than zeroing it.

Empty means the empty string, a zero number, false, or an empty slice or map. required and omitempty on the same field are not contradictory: the value must be present, and if it is present but empty the field is left alone.

Tag options binding at all is new in 1.1.0. Before that, body:"email,omitempty" looked for a key literally named email,omitempty and so never bound anything. Fields written that way will start receiving values on upgrade.

Per-call options

BindWithOptions is Bind with configuration. The zero BindOptions behaves exactly as Bind does, so only the fields you care about need setting:

opts := binder.BindOptions{
	MaxBodySize:           1 << 20, // 1 MB for this call only
	DisallowUnknownFields: true,    // reject body keys nothing binds
}

if err := binder.BindWithOptions(r, &req, opts); err != nil {
	// ...
}
FieldDefaultEffect
MaxBodySize0 Overrides the package-level binder.MaxBodySize for this call. Zero leaves the package setting in force; a negative value removes the limit for this call alone.
DisallowUnknownFieldsfalse Fails with ErrUnknownField when the body carries a top-level key that no field of the target binds. Keys nested inside objects are not inspected.

Rejecting unknown fields

Useful on write endpoints, where a client sending emial would otherwise get a cheerful 200 and no email change. It is a per-call option rather than the default because a rejected typo is the right answer for an internal API and the wrong one for a public endpoint that has to tolerate clients sending more than it reads.

if errors.Is(err, binder.ErrUnknownField) {
	http.Error(w, err.Error(), http.StatusBadRequest)
	return
}

Body size limits

Bodies are capped at binder.MaxBodySize, which defaults to DefaultMaxBodySize — 10 MB. An oversized body is rejected with ErrBodyTooLarge rather than truncated, so a request is never bound from half a body. The limit is enforced while reading rather than trusted from Content-Length, which the client controls and may understate.

Set it once during initialisation to change the default everywhere:

func init() {
	binder.MaxBodySize = 2 << 20 // 2 MB
}

Zero or less removes the limit entirely and restores the unbounded behaviour of 1.0. The variable is read on every call, so set it at startup rather than while requests are in flight.

The default value is not part of the compatibility contract — set it explicitly if your service depends on a particular limit. See Compatibility.