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
- Only put JSON-serializable primitives (string, number, bool, time-as-string, simple maps/slices) into the cursor values map.
- If a custom type must be used, give it a working MarshalJSON or pre-convert it to a plain map.
- 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
- Only store JSON primitives and simple maps/slices in cursors.
- Unit-test SetNextCursor with real pagination payloads.
- Avoid funcs, channels, and unexported-field structs in cursor values.
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
- paginate: failed to encode cursor values
- sse: marshal data: %w
- fiber: failed to encode shared state %s value: %w
- cache: failed to unmarshal key %q: %w
- cache: failed to marshal key %q: %w
AI-assisted analysis of gofiber/fiber@9a4c7e57fe (2026-08-04).
Data as JSON: /data/errors/b7fcf69cdf89e7f6.json.
Report an issue: GitHub.