laravel/framework · error · RuntimeException

You must specify an orderBy clause when using this function.

Error message

You must specify an orderBy clause when using this function.

What it means

Thrown by enforceOrderBy when both $this->orders and $this->unionOrders are empty. The guard is invoked by ensureOrderForCursorPagination, so cursorPaginate/cursorPaginateUsingCursor require a deterministic row order to compute stable cursors. Without an orderBy the cursor has nothing to anchor against and pagination would return arbitrary or duplicated rows across pages.

Source

Thrown at src/Illuminate/Database/Query/Builder.php:3811

            yield from $this->connection->cursor(
                $this->toSql(), $this->getBindings(), ! $this->useWritePdo, $this->fetchUsing
            );
        }))->map(function ($item) {
            return $this->applyAfterQueryCallbacks(new Collection([$item]))->first();
        })->reject(fn ($item) => is_null($item));
    }

    /**
     * Throw an exception if the query doesn't have an orderBy clause.
     *
     * @return void
     *
     * @throws \RuntimeException
     */
    protected function enforceOrderBy()
    {
        if (empty($this->orders) && empty($this->unionOrders)) {
            throw new RuntimeException('You must specify an orderBy clause when using this function.');
        }
    }

    /**
     * Get a collection instance containing the values of a given column.
     *
     * @param  \Illuminate\Contracts\Database\Query\Expression|string  $column
     * @param  string|null  $key
     * @return \Illuminate\Support\Collection<array-key, mixed>
     */
    public function pluck($column, $key = null)
    {
        $original = $this->columns;

        // First, we will need to select the results of the query accounting for the
        // given columns / key. Once we have the results, we will be able to take
        // the results and get the exact data that was requested for the query.
        $this->columns ??= is_null($key) || $key === $column

View on GitHub (pinned to bd6b5437e6)

Solutions

  1. Add an orderBy before cursorPaginate: `Model::orderBy('id')->cursorPaginate()`.
  2. Prefer ordering by a unique, monotonic column (id, created_at with id tiebreaker) for stable cursors.
  3. For unions, order the outer query: `DB::table(...)->union(...)->orderBy('id')->cursorPaginate()`.
  4. If order is genuinely irrelevant, use simple `paginate()`/`get()` instead of cursor pagination.

Example fix

// before
Model::cursorPaginate(20);
// => You must specify an orderBy clause when using this function.

// after
Model::orderBy('id')->cursorPaginate(20);
Defensive patterns

Strategy: validation

Validate before calling

if (empty($query->orders) && empty($query->unionOrders)) {
    $query->orderBy($model->getKeyName());
}
$model::cursorPaginate();

Type guard

function hasOrderBy(\Illuminate\Database\Query\Builder $q): bool
{
    return ! empty($q->orders) || ! empty($q->unionOrders);
}

Try / catch

try {
    $results = $query->cursorPaginate();
} catch (\RuntimeException $e) {
    if (str_contains($e->getMessage(), 'orderBy clause')) {
        $query->orderBy($query->from.'.id');
        $results = $query->cursorPaginate();
    } else { throw $e; }
}

Prevention

When it happens

Trigger: Calling `Model::cursorPaginate()` without any prior `->orderBy(...)`. Using cursor pagination on a view or query that has no natural ordering. Chaining `->orderByRaw('')` (which adds nothing). Switching from paginate() to cursorPaginate() on an unordered query.

Common situations: Adopting cursor pagination for performance on an existing endpoint that never specified order; paginating a UNION query where orders are set on a subquery not the outer builder; default scope stripping order.

Related errors


AI-assisted analysis of laravel/framework@bd6b5437e6 (2026-08-06). Data as JSON: /data/errors/e6570e8c8b1e3935.json. Report an issue: GitHub.