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

  1. Set a DefaultPageToken with at least one column on every plan passed to NewPaginationPlanner
  2. If no natural token exists, define one over a unique/sorted column set so ordering is deterministic
  3. 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

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


AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03). Data as JSON: /api/errors/9599635783883c51. Report an issue: GitHub.