ory/hydra · error
plan must define a DefaultPageToken
Error message
plan must define a DefaultPageToken
What it means
NewPaginationPlanner validates each pagination plan before constructing the planner. Every plan must declare a DefaultPageToken with at least one column, because the planner needs a fallback token to determine default ordering/pagination. A plan missing a usable DefaultPageToken makes deterministic pagination impossible, so construction fails fast.
Source
Thrown at oryx/pagination/paginationplanner/planner.go:31
// Plan is eligible when its required constraint set is satisfied (and an optional Condition matches).
// If no plan matches, the FallbackPlan is used.
type PaginationPlanner struct {
Plans []PaginationPlan
FallbackPlan PaginationPlan
}
func NewPaginationPlanner(fallbackPlan PaginationPlan, plans []PaginationPlan) (*PaginationPlanner, error) {
if len(fallbackPlan.DefaultPageToken.Columns()) == 0 {
return nil, errors.New("plan must define at least one PageTokenColumn")
}
for i := range plans {
plan := &plans[i]
if len(plan.ApplicableQueries) == 0 {
return nil, errors.New("plan must define at least one ApplicableQueries")
}
if len(plan.DefaultPageToken.Columns()) == 0 {
return nil, errors.New("plan must define a DefaultPageToken")
}
plan.populateInternals()
}
return &PaginationPlanner{
Plans: plans,
FallbackPlan: fallbackPlan,
}, nil
}
// GetPaginator selects the first eligible plan for the given queriedColumns constraints.
// Eligibility requires an exact ColumnSet match and (if set) Condition match.
// If none match, the FallbackPlan is used for building the Paginator.
func (p *PaginationPlanner) GetPaginator(q Query, pageOpts ...keysetpagination.Option) (*keysetpagination.Paginator, error) {
if len(q) == 0 {
return keysetpagination.NewPaginator(append(pageOpts, keysetpagination.WithDefaultToken(p.FallbackPlan.DefaultPageToken))...)
}
View on GitHub (pinned to 4174065ffb)
Solutions
- Set a DefaultPageToken with at least one column on every plan passed to NewPaginationPlanner
- If no natural token exists, define one over a unique/sorted column set so ordering is deterministic
- Add a unit test constructing the planner over all production plans to catch misconfiguration at startup
Example fix
// before
plans := []Plan{{ApplicableQueries: []string{"ListUsers"}}}
planner, err := NewPaginationPlanner(plans) // error
// after
plans := []Plan{{ApplicableQueries: []string{"ListUsers"}, DefaultPageToken: NewPageToken([]Column{{Name: "id", Direction: Ascending}})}}
planner, err := NewPaginationPlanner(plans) Defensive patterns
Strategy: validation
Validate before calling
for i, plan := range plans {
if len(plan.ApplicableQueries) == 0 || len(plan.DefaultPageToken.Columns()) == 0 {
return fmt.Errorf("plan %d: needs ApplicableQueries and a non-empty DefaultPageToken", i)
}
} Type guard
func planIsConfigured(p Plan) bool {
return len(p.ApplicableQueries) > 0 && len(p.DefaultPageToken.Columns()) > 0
} Try / catch
planner, err := NewPaginationPlanner(plans)
if err != nil {
return nil, fmt.Errorf("pagination misconfiguration: %w", err)
} Prevention
- Always initialize DefaultPageToken with at least one column when defining a plan
- Share a helper/constructor for building plans so tokens cannot be forgotten
- Add a startup test that constructs the planner over all registered plans
When it happens
Trigger: Calling NewPaginationPlanner with a PaginationPlan whose DefaultPageToken has zero columns — typically a zero-value PageToken{} or a token constructed without any Column entries.
Common situations: Defining plans structurally without initializing the token (e.g. plan := Plan{ApplicableQueries: ...} with no DefaultPageToken field set); copy-pasting a plan and forgetting the token; a refactor that changed token construction to return an empty token on some code path.
Related errors
- cookiex: purpose must be non-empty and must not contain a pi
- cookiex: at least one secret is required
- file loader disabled
- http(s) loader disabled
- the provided region is not a valid Ory region
AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03).
Data as JSON: /api/errors/9599635783883c51.
Report an issue: GitHub.