gofiber/fiber · error

ErrCursorEncode

ErrCursorEncode

Error message

%w: %w

What it means

Returned by PageInfo.SetNextCursor when the values map cannot be marshaled to JSON. SetNextCursor encodes a caller-supplied map[string]any into an opaque base64 cursor; if json.Marshal fails it wraps the error with the ErrCursorEncode sentinel.

Source

Thrown at middleware/paginate/page_info.go:212

	if err := unmarshal(data, &values); err != nil {
		return nil
	}

	p.cursorData = values

	return values
}

// SetNextCursor encodes a key-value map into an opaque cursor token
// and sets both NextCursor and HasMore on the PageInfo.
func (p *PageInfo) SetNextCursor(values map[string]any) error {
	marshal := p.jsonMarshal
	if marshal == nil {
		marshal = json.Marshal
	}
	data, err := marshal(values)
	if err != nil {
		return fmt.Errorf("%w: %w", ErrCursorEncode, err)
	}

	encoded := base64.RawURLEncoding.EncodeToString(data)
	if len(encoded) > maxCursorLen {
		return fmt.Errorf("%w: cursor token exceeds maximum length (%d)", ErrCursorEncode, maxCursorLen)
	}

	p.NextCursor = encoded
	p.HasMore = true

	return nil
}

View on GitHub (pinned to 9a4c7e57fe)

Solutions

  1. Only put JSON-serializable primitives (string, number, bool, time-as-string, simple maps/slices) into the cursor values map.
  2. If a custom type must be used, give it a working MarshalJSON or pre-convert it to a plain map.
  3. Test cursor encoding in unit tests with the exact value shapes you paginate on.

Example fix

// before: storing a non-marshalable value
page.SetNextCursor(map[string]any{
    "after": someFunc, // func value -> marshal error
})

// after: store a plain serializable value
page.SetNextCursor(map[string]any{
    "after": lastID, // string/int
})
Defensive patterns

Strategy: validation

Validate before calling

// Validate the cursor map is JSON-marshalable before calling SetNextCursor.
func safeSetCursor(p *paginate.PageInfo, values map[string]any) error {
    if _, err := json.Marshal(values); err != nil {
        return fmt.Errorf("cursor values not serializable: %w", err)
    }
    return p.SetNextCursor(values)
}

Type guard

// isJSONScalar narrows a single value to the types json.Marshal always
// accepts without a custom marshaler.
func isJSONScalar(v any) bool {
    switch v.(type) {
    case string, bool, float64, float32, int, int32, int64,
        uint, uint32, uint64, json.Number, nil:
        return true
    }
    return false
}

Try / catch

if err := page.SetNextCursor(values); err != nil {
    if errors.Is(err, paginate.ErrCursorEncode) {
        // fall back to page-based pagination
        page.HasMore = true
    }
}

Prevention

When it happens

Trigger: Calling pageInfo.SetNextCursor(values) where values contains a value that json.Marshal cannot encode: a func, chan, complex, a struct with an unexported/non-marshalable field, or a cyclic reference.

Common situations: Storing a function or channel in the cursor map; storing a struct with unexported fields; storing a map with a key/value that has a custom MarshalJSON that errors; accidental inclusion of non-serializable domain objects.

Related errors


AI-assisted analysis of gofiber/fiber@9a4c7e57fe (2026-08-04). Data as JSON: /data/errors/b7fcf69cdf89e7f6.json. Report an issue: GitHub.