Model::latest

Orders results newest first by the model's created_at column, or by a column you name.

Signature
public function latest(string|\Illuminate\Contracts\Database\Query\Expression|null $column = null): $this

Parameters

ParameterTypeDescription
$columnoptionalstring|\Illuminate\Contracts\Database\Query\Expression|null= nullThe column to order by descending. When null on an Eloquent builder, the model's CREATED_AT column is used, falling back to 'created_at'. The underlying query builder defaults to the literal 'created_at'.

Return value

$thisThe same builder instance, so calls can be chained. Appends a descending order by clause.

Errors

Throws Illuminate\Database\QueryException when the resolved column does not exist on the table.

latest() is sugar for orderBy($column, 'desc') aimed at date columns. Called with no argument on an Eloquent builder, it asks the model for its CREATED_AT constant and falls back to 'created_at'. oldest() is the ascending twin.

When to reach for it

Activity feeds, dashboards, "most recent" widgets — anywhere newest-first is the natural order. Pairing it with first() is the idiomatic "grab the newest row" query.

What trips people up

The default column is created_at, not the primary key. On a model with public $timestamps = false; — or a legacy table whose date column is named something else — the column does not exist and you get a QueryException at execution time, not at build time. Pass the real column, or set CREATED_AT on the model.

created_at is rarely unique. Rows inserted in the same second, or in a bulk insert sharing one timestamp, have no defined order among themselves. Under pagination that produces rows appearing twice or never. Add ->orderByDesc('id') as a tie-break.

It appends an ordering rather than replacing one. Calling it after another orderBy() makes it the secondary sort, which is usually not what was intended — use reorder() first if you need it to lead.

Example

Example · php
<?php

use App\Models\Post;

// Newest first, using the model's created_at column.
$recent = Post::latest()->limit(20)->get();
// select * from posts order by created_at desc limit 20

foreach ($recent as $post) {
    echo $post->created_at->toDateTimeString(), ' ', $post->title, "\n";
}

// The idiomatic "newest single row".
$newest = Post::latest()->first();

echo $newest?->title, "\n";

// Name a different column.
$events = Event::latest('occurred_at')->get();

// oldest() is the ascending twin.
$first = Post::oldest()->first();

echo $first?->title, "\n";

When to use it

  • Show the twenty most recent orders on a dashboard.
  • Fetch a user's newest notification with latest()->first().
  • Sort an audit log by a domain timestamp such as occurred_at rather than created_at.

Frequently asked questions

What column does latest() sort by?

By default the model's CREATED_AT column, which is created_at unless the model overrides the constant; the underlying query builder uses the literal 'created_at'. Pass a column name to override it — latest('occurred_at') — or define const CREATED_AT = 'dispatched_at' on the model so every bare call resolves correctly. It is not the primary key, so a model without timestamps will fail.

What is the difference between latest() and orderByDesc()?

None mechanically — latest($column) calls orderBy($column, 'desc'). The difference is the default: latest() falls back to the model's created_at column when you pass nothing, while orderByDesc() always requires an explicit column. Use latest() when you mean "newest first by creation time" and orderByDesc() for any other descending sort.

Why does latest() throw a QueryException?

Because the resolved column does not exist on the table — usually a model with public $timestamps = false, or a legacy schema whose date column is named something like placed_on. latest() picks its default column without consulting the schema, so the failure surfaces at execution as a driver error. Pass the real column name or set the CREATED_AT constant on the model.

More examples

Ties, pagination, and the fix

The first two pages are the duplicate-rows bug: created_at has second-level granularity at best, so tied rows are returned in whatever order the plan happens to produce. Adding the descending primary key gives every row a unique position, and the printed ids are strictly decreasing.

Example · php
<?php

use App\Models\Notification;
use Illuminate\Support\Carbon;

Carbon::setTestNow('2026-03-01 12:00:00');

// A bulk insert gives many rows the same created_at.
// Their relative order is undefined between queries.
$page1 = Notification::latest()->paginate(20);
$page2 = Notification::latest()->paginate(20, page: 2);
// A notification can show on both pages, or on neither.

echo $page1->total(), "\n";

// A unique tie-break makes the ordering total and stable.
$stable = Notification::latest()
    ->orderByDesc('id')
    ->paginate(20);

foreach ($stable as $n) {
    echo $n->id, ' ', $n->created_at->toDateTimeString(), "\n";
}

When created_at is not the column

The commented call fails at execution time with a driver-level error, because latest() resolves its default column before the query is sent but never checks the schema. Passing the column explicitly, or defining CREATED_AT on the model, both produce a working query — the constant is preferable when the whole model consistently uses that column.

Example · php
<?php

use App\Models\LegacyOrder;
use Illuminate\Database\Eloquent\Model;

class LegacyOrder extends Model
{
    protected $table = 'orders_v1';
    public $timestamps = false;
}

// QueryException: Unknown column 'created_at'
// LegacyOrder::latest()->first();

// Name the real column instead.
$order = LegacyOrder::latest('placed_on')->first();
echo $order?->reference, "\n";

// Or teach the model which column means "created".
class Shipment extends Model
{
    const CREATED_AT = 'dispatched_at';
}

// Now the bare call resolves to dispatched_at.
$shipment = Shipment::latest()->first();
echo $shipment?->tracking_number, "\n";

Ordering is appended, not replaced

Read the two compiled order-by clauses: chain position decides precedence, so a latest() added after an existing sort only breaks ties. reorder() strips whatever ordering a global scope or a local scope contributed, letting latest() take the lead unambiguously.

Example · php
<?php

use App\Models\Task;

// latest() becomes the SECOND sort key here.
$tasks = Task::orderBy('priority')
    ->latest()
    ->get();
// order by priority asc, created_at desc

echo $tasks->count(), "\n";

// To make it lead, put it first.
$tasks = Task::latest()
    ->orderBy('priority')
    ->get();
// order by created_at desc, priority asc

// Clear an ordering a scope already applied.
$tasks = Task::query()->reorder()->latest()->get();

foreach ($tasks as $task) {
    echo $task->created_at->toDateTimeString(), "\n";
}

Discussion

  • Be the first to comment on this lesson.