gofiber/fiber · error · ErrCursorEncode

%w: cursor token exceeds maximum length (%d)

Error message

%w: cursor token exceeds maximum length (%d)

What it means

After successful JSON marshal, the base64-rawurl-encoded cursor must fit within maxCursorLen (2048 chars, defined in paginate.go:24). If the encoded token exceeds this, SetNextCursor refuses it to keep cursors URL-safe and to bound the size of state stored client-side.

Source

Thrown at middleware/paginate/page_info.go:217

	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 a105acad6c)

Solutions

  1. Reduce the cursor payload to the minimum columns needed to resume: typically the sort key of the last row plus its unique id.
  2. Move large filter state server-side (store a filter id in the cursor, keep the actual filter in a server cache keyed by that id).
  3. If you genuinely need more state, raise the local cap by maintaining a fork or PR — but 2048 is intentionally conservative for URL safety across proxies.
  4. Log len(encoded) at debug level to see how close to the limit you are in production.
  5. Avoid base64-encoding binary data inside cursor values; reference it by id instead.

Example fix

// before: entire filter object stored in cursor
values := map[string]any{"filter": bigFilterMap, "id": lastID, "sort": sort}
pi.SetNextCursor(values) // exceeds 2048

// after: store a server-side filter id, keep cursor minimal
filterID := cache.Put(bigFilterMap)
values := map[string]any{"f": filterID, "id": lastID}
pi.SetNextCursor(values)
Defensive patterns

Strategy: validation

Validate before calling

// Encode early and check size before committing.
const maxCursorLen = 2048 // mirror paginate.go
func safeSetCursor(pi *paginate.PageInfo, values map[string]any) error {
  data, _ := json.Marshal(values)
  if len(base64.RawURLEncoding.EncodeToString(data)) > maxCursorLen {
    return errors.New("cursor too large; reduce payload")
  }
  return pi.SetNextCursor(values)
}

Try / catch

if err := pi.SetNextCursor(values); err != nil {
  if errors.Is(err, paginate.ErrCursorEncode) { /* drop cursor, return last page */ }
}

Prevention

When it happens

Trigger: A values map whose JSON form is large enough that its base64 encoding exceeds 2048 chars. Typical: a map with >100 keys, very long string values (e.g. full URLs or BLOBs base64-encoded twice), or deeply nested structures. The threshold is roughly 1.5KB of JSON payload.

Common situations: Storing full filter objects or search queries in the cursor; embedding user-supplied free-text in cursor values; paginating over a column set large enough that row identifiers plus sort keys exceed the budget.

Related errors


AI-assisted analysis of gofiber/fiber@a105acad6c (2026-08-11). Data as JSON: /api/errors/ec6c8db0f7eb15cd. Report an issue: GitHub.