{"record":{"id":"ca9016054df7dff0","repo":"ory/hydra","slug":"cannot-marshal-page-token","errorCode":null,"errorMessage":"cannot marshal page token","messagePattern":"cannot marshal page token","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"oryx/pagination/keysetpagination_v2/page_token.go","lineNumber":180,"sourceCode":"\t\t}\n\t}\n\n\tnow := time.Now\n\tif t.testNow != nil {\n\t\tnow = t.testNow\n\t}\n\tif rawToken.ExpiresAt.Before(now().UTC()) {\n\t\treturn errors.WithStack(ErrPageTokenExpired())\n\t}\n\treturn nil\n}\n\nfunc NewPageToken(cols ...Column) PageToken { return PageToken{cols: cols} }\n\nfunc (t *PageToken) encrypt(key [32]byte) (string, error) {\n\traw, err := json.Marshal(t)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"cannot marshal page token\")\n\t}\n\n\ta, err := aead.New(key)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"cannot create AEAD\")\n\t}\n\n\t// The nonce is prepended to the ciphertext. AEADs that manage the nonce\n\t// internally report a nonce size of zero, so this also covers them.\n\tnonce := make([]byte, a.NonceSize())\n\tif _, err := rand.Read(nonce); err != nil {\n\t\treturn \"\", errors.Wrap(err, \"cannot generate nonce\")\n\t}\n\n\treturn base64.URLEncoding.EncodeToString(a.Seal(nonce, nonce, raw, []byte(pageTokenContext))), nil\n}\n\nfunc (t *PageToken) decrypt(key [32]byte, s string) error {","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/ory/hydra/blob/4174065ffb052799890f7480f5360a877a67ffc1/oryx/pagination/keysetpagination_v2/page_token.go#L162-L198","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\ntok := NewPageToken(Column{Name: \"data\", Value: someChannel})\nenc, err := Encrypt(key, tok)\n// after\ntok := NewPageToken(Column{Name: \"data\", Value: fmt.Sprint(someValue)})\nenc, err := Encrypt(key, tok)","handlingStrategy":"try-catch","validationCode":"func isJSONSerializable(v interface{}) error {\n    _, err := json.Marshal(v)\n    return err\n}","typeGuard":null,"tryCatchPattern":"enc, err := Encrypt(key, token)\nif err != nil && strings.Contains(err.Error(), \"cannot marshal page token\") {\n    return fmt.Errorf(\"page token contains non-JSON value: %w\", err)\n}","preventionTips":["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"],"tags":["json","serialization","pagination"],"backgroundTag":"json-marshal-failed","analyzedSha":"4174065ffb052799890f7480f5360a877a67ffc1","analyzedAt":"2026-09-03T14:52:41.581Z","contentChangedAt":"2026-09-03T14:52:41.581Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}