ory/hydra · error
cannot marshal page token
Error message
cannot marshal page token
What it means
PageToken.encrypt serializes the pagination cursor (columns/values) to JSON before sealing it with AEAD encryption. If json.Marshal fails, the token cannot be produced and this error is returned. In practice the PageToken struct is JSON-safe, so this almost always indicates custom/unmarshalable data was placed into the token (e.g. a channel, func, or invalid value in a column value) or a nil receiver misuse.
Source
Thrown at oryx/pagination/keysetpagination_v2/page_token.go:180
}
}
now := time.Now
if t.testNow != nil {
now = t.testNow
}
if rawToken.ExpiresAt.Before(now().UTC()) {
return errors.WithStack(ErrPageTokenExpired())
}
return nil
}
func NewPageToken(cols ...Column) PageToken { return PageToken{cols: cols} }
func (t *PageToken) encrypt(key [32]byte) (string, error) {
raw, err := json.Marshal(t)
if err != nil {
return "", errors.Wrap(err, "cannot marshal page token")
}
a, err := aead.New(key)
if err != nil {
return "", errors.Wrap(err, "cannot create AEAD")
}
// The nonce is prepended to the ciphertext. AEADs that manage the nonce
// internally report a nonce size of zero, so this also covers them.
nonce := make([]byte, a.NonceSize())
if _, err := rand.Read(nonce); err != nil {
return "", errors.Wrap(err, "cannot generate nonce")
}
return base64.URLEncoding.EncodeToString(a.Seal(nonce, nonce, raw, []byte(pageTokenContext))), nil
}
func (t *PageToken) decrypt(key [32]byte, s string) error {View on GitHub (pinned to 4174065ffb)
Solutions
- Inspect the PageToken columns/values for types json.Marshal cannot encode (channel, func, map with non-string keys)
- Convert values to JSON-safe primitives (string, int64, time.Time) before building the token
- Log the underlying wrapped error to see which field failed (json: unsupported type: ...)
- Update oryx/pagination to latest in case of a fixed serialization bug
Example fix
// before
tok := NewPageToken(Column{Name: "data", Value: someChannel})
enc, err := Encrypt(key, tok)
// after
tok := NewPageToken(Column{Name: "data", Value: fmt.Sprint(someValue)})
enc, err := Encrypt(key, tok) Defensive patterns
Strategy: try-catch
Validate before calling
func isJSONSerializable(v interface{}) error {
_, err := json.Marshal(v)
return err
} Try / catch
enc, err := Encrypt(key, token)
if err != nil && strings.Contains(err.Error(), "cannot marshal page token") {
return fmt.Errorf("page token contains non-JSON value: %w", err)
} Prevention
- Keep PageToken column values to JSON-safe primitives (string, int64, time.Time)
- Sanitize custom column values before building the token
- Log the wrapped cause to identify the offending field type
- Add a unit test that encrypts tokens built from every column type you use
When it happens
Trigger: Calling Encrypt (which calls encrypt) with a PageToken whose column values include types json.Marshal cannot encode — channels, functions, complex, or cyclic data — typically injected via custom column definitions or values.
Common situations: Storing non-primitive values (time with unusual type wrapper, custom struct without json tags that contains unsupported fields) in a keyset pagination column; misuse of the pagination API passing raw DB values that are not JSON-serializable.
Understand the failure class
Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.
Related errors
- failed to encode JSON Web Key Set
- jsonnetsecure: marshal
- cookiex: payload must be a flat JSON object with string valu
- plan must define a DefaultPageToken
- unable to decode JSON: %w
AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03).
Data as JSON: /api/errors/ca9016054df7dff0.
Report an issue: GitHub.