Querying records from the last 30 days in Laravel is a common reporting requirement — dashboards, recent activity feeds, email digests. Carbon is Laravel’s built-in date library. Carbon::now()->subDays(30) gives you a timestamp 30 days back from right now, which you pass directly to Eloquent’s query builder. All three methods below work on Laravel 10, 11, and 12.
:::note[TL;DR]
Carbon::now()->subDays(30)returns a timestamp 30 days ago from the current momentwhere('created_at', '>=', Carbon::now()->subDays(30))is the simplest formwhereBetween()makes both bounds explicit — useful when you’re logging or debugging the query window- A query scope (
scopeLastDays) centralises the logic so you don’t copy-paste Carbon across controllers - Use
->count()instead of->get()for dashboard number cards — skips Eloquent hydration entirely :::
How do you fetch records from the last 30 days using where()?
This is the most direct approach. One line, no fuss. Pass a Carbon timestamp as the lower bound of a where clause.
Before writing the query, import Carbon at the top of your controller or model file:
use Carbon\Carbon;
Then query with a simple >= comparison:
$users = User::where('created_at', '>=', Carbon::now()->subDays(30))->get();
This fetches every record where created_at is on or after exactly 30 days ago from this moment. The upper bound is now — anything created more than 30 days ago is excluded.
The Scenario: Your admin dashboard shows “new signups in the last 30 days.” One line. Returns an Eloquent collection you can loop over, paginate, or pass to a chart. No date math by hand, no raw SQL.
How does whereBetween() differ from a simple where()?
whereBetween() makes both bounds explicit — the start and end of the window. Functionally equivalent to the where >= approach for this use case, but the intent is clearer in code review.
use Carbon\Carbon;
$orders = Order::whereBetween('created_at', [
Carbon::now()->subDays(30), // lower bound
Carbon::now(), // upper bound (now)
])->get();
Use this when you’re logging the exact window to a query log, when you’re debugging an unexpected result, or when another developer needs to understand the time range at a glance without reconstructing it mentally.
How do you create a reusable query scope for this?
If three controllers each need “last N days” queries, a query scope keeps the logic in one place. Define it on the model once, call it anywhere.
First, add the scope method to the model. The method name starts with scope and takes a Builder instance plus any parameters:
// app/Models/User.php
use Illuminate\Database\Eloquent\Builder;
use Carbon\Carbon;
public function scopeLastDays(Builder $query, int $days): Builder
{
return $query->where('created_at', '>=', Carbon::now()->subDays($days));
}
Then call it without the scope prefix — Laravel strips it automatically:
// Last 30 days of users
$users = User::lastDays(30)->get();
// Last 7 days of pending orders — chain other constraints freely
$orders = Order::lastDays(7)->where('status', 'pending')->get();
The Scenario: Three different controllers each need “last N days” queries. Without a scope, you copy-paste the Carbon logic each time. One month later, someone changes 30 to 31 in one place and forgets the other two. The results quietly diverge. The scope makes that impossible — one change, consistent everywhere.
Scopes also pair well with whereDate queries when you want to combine a date range with a specific date match.
How do you count records instead of fetching them?
For dashboard number cards, skip ->get() entirely and use ->count(). No Eloquent model hydration, no collection overhead — just a fast SQL COUNT(*).
$count = User::where('created_at', '>=', Carbon::now()->subDays(30))->count();
This returns an integer directly. Use it when you need to display “342 signups this month” without needing the actual records.
The same pattern works with the query scope:
$count = User::lastDays(30)->count();
:::warning
Carbon::now() uses the app’s configured timezone from config/app.php (timezone key). If your server and your database are running in different timezones, results may be off by hours — or worse, miss an entire day at the boundary. Use Carbon::now() consistently throughout your queries. Don’t mix it with raw date() PHP calls in the same filter, or you’ll get results that look right most of the time and wrong at midnight.
:::
For checking whether a specific record’s date falls before now, see how to check for a past date in Laravel.
Summary
where('created_at', '>=', Carbon::now()->subDays(30))is the simplest last-30-days querywhereBetween()makes both bounds visible — easier to reason about in complex queries- A
scopeLastDays(Builder $query, int $days)scope centralises the logic across the entire app - Use
->count()over->get()when you only need a number — it’s significantly faster on large tables
FAQ
Does this work with columns other than created_at?
Yes. Replace created_at with any DATETIME or TIMESTAMP column — updated_at, published_at, deleted_at, anything. The Carbon comparison is column-agnostic.
What’s the difference between subDays(30) and subMonth()?
subDays(30) always goes back exactly 30 days. subMonth() goes back one calendar month, which is 28–31 days depending on the month. For consistent reporting windows, use subDays(). For “same day last month” comparisons, use subMonth().
Can I use the query scope on any model?
The scope is defined on a specific model class. To reuse it across multiple models, extract it into a trait (e.g., HasDateScopes) and add use HasDateScopes to each model that needs it.
Will this query use an index?
Only if created_at is indexed. On most Laravel tables it isn’t by default, since id is the only auto-indexed column. For tables where you frequently filter by date, add an index via migration. See create indexes in Laravel migrations.
How do I get the last 30 days grouped by day?
Use selectRaw('DATE(created_at) as date, COUNT(*) as total'), combine it with the where filter, and add ->groupBy('date')->orderBy('date'). Laravel doesn’t have a dedicated “group by day” method — raw SQL expressions are the right tool here.
What to Read Next
- whereDate method in Laravel — filter by a specific calendar date instead of a timestamp range
- whereTime method in Laravel — combine with date filters when you need time-of-day precision
- Check for past date in Laravel validation — validate that a submitted date is actually in the past