M
MeshWorld.
Laravel Validation PHP Forms 6 min read

Laravel Validation: Allow Only Past Dates Before Today

Vishnu
By Vishnu
| Updated: Mar 27, 2026

Laravel’s date validation rules handle past-date enforcement without custom logic or Carbon comparisons in the controller. The before:today rule does exactly what it says: fails validation if the submitted date is today or in the future. Works on Laravel 10, 11, and 12.

:::note[TL;DR]

  • before:today rejects today and any future date — strict past-only
  • before_or_equal:today allows today but blocks future dates
  • before:start_date compares against another field in the same request
  • Use date_format:Y-m-d before the before:today check to enforce a specific format
  • Laravel 11+ has a fluent Rule::date()->before('today') API that reads more clearly when chaining :::

What rule strings enforce a past date in Laravel validation?

These four rule strings cover the most common past-date scenarios. Paste the one that fits your use case directly into a rules array.

// Any date before today — strict, rejects today itself
'birthday' => 'required|date|before:today',

// With a specific input format enforced first
'birthday' => 'required|date_format:Y-m-d|before:today',

// Allow today but reject future dates
'start_date' => 'required|date|before_or_equal:today',

// Must come before another field in the same request
'end_date' => 'required|date|before:start_date',

before:today and before_or_equal:today both accept Carbon-parseable date strings. The comparison runs against the server’s current date at validation time. If you specify date_format:Y-m-d, the input must match that exact format before before:today even fires — a badly formatted date fails on date_format, not on before.

The Scenario: You have a birthday field on a registration form. Without validation, users accidentally — or deliberately — submit a birth year of 2045. before:today rejects it automatically and returns a validation error. You don’t write any of this logic yourself.

How do you use a Form Request class for past-date validation?

Form Request classes keep validation rules out of the controller. Generate the class first, then define the rules inside it.

Run the artisan command to create the file:

php artisan make:request UserStoreRequest

Then define the rules and a custom error message:

// app/Http/Requests/UserStoreRequest.php
use Illuminate\Foundation\Http\FormRequest;

class UserStoreRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'name'     => ['required', 'string', 'max:255'],
            'birthday' => ['required', 'date_format:Y-m-d', 'before:today'],
        ];
    }

    public function messages(): array
    {
        return [
            'birthday.before' => 'Birthday must be a date in the past.',
        ];
    }
}

Type-hint UserStoreRequest in your controller method instead of Request. Laravel resolves and runs validation automatically — the controller method only runs if validation passes.

The custom message key format is field.rule. So birthday.before targets the before rule on the birthday field. All other rules use Laravel’s default messages unless you override them.

How do you customise the validation error message?

Two places to set it. The messages() method in the Form Request (shown above) is the most contained approach. For a site-wide override, edit lang/en/validation.php and change the before key:

// lang/en/validation.php
'before' => 'The :attribute must be a date before :date.',

The :date placeholder is replaced by whatever you passed to the rule — in this case, today. The :attribute placeholder is replaced by the field name, formatted using the attributes array in the same file if you’ve defined a label there.

For API responses, the validation error is returned as a JSON object automatically when the request’s Accept header is application/json. No extra setup needed.

How does the fluent Rule::date() API work in Laravel 11+?

Laravel 11 added a fluent date rule builder under Rule::date(). It produces identical validation behaviour — it’s just easier to read when chaining multiple constraints.

Import the Rule class, then chain your constraints:

use Illuminate\Validation\Rule;

'birthday' => Rule::date()->format('Y-m-d')->before('today'),

The fluent API shines when you have multiple date constraints on one field. Compare this:

// String rule — harder to parse at a glance
'birthday' => 'required|date_format:Y-m-d|before:today|after:1900-01-01',

// Fluent rule — reads like a sentence
'birthday' => ['required', Rule::date()->format('Y-m-d')->before('today')->after('1900-01-01')],

Both produce the same result. Use whichever your team finds more readable.

If you also need conditional validation — for example, birthday is only required when another field has a specific value — combine this with required_if with direct conditions.

:::warning before:today compares against the server’s date at the time of validation, not the user’s local date. For forms submitted around midnight by international users, the server might be on a different calendar day than the user expects. If exact-day precision matters for your use case, add a timezone field to the form and apply Carbon::createFromFormat('Y-m-d', $date)->setTimezone($tz) before the validation check runs. :::

Summary

  • before:today rejects today and any future date; before_or_equal:today allows today
  • date_format:Y-m-d should come before before:today to enforce the format first
  • Form Request classes keep validation rules out of controllers and make custom messages easy
  • Laravel 11+ Rule::date()->before('today') is a fluent alternative to string rules
  • Timezone differences between server and user can cause off-by-one-day edge cases at midnight

FAQ

What’s the difference between before:today and before:now? before:today compares against the calendar date with no time component — midnight today. before:now compares against the exact current timestamp. For birthday fields, use before:today. For timestamp fields where even earlier today should be rejected, use before:now.

Can I use a dynamic date instead of today? Yes. Pass a Carbon-parseable string or a date computed in your controller. For example: 'start_date' => 'before:' . now()->addDays(7)->toDateString() enforces that the date is within the next 7 days. String rules support dynamic values via concatenation.

Does before:today work with datetime-local inputs? Yes, with caveats. If the input includes a time component (e.g., 2024-01-15T14:30), Laravel parses the full datetime. before:today then compares the datetime against today at midnight — so a time earlier today would pass. Use before:now if you want to reject anything earlier than this exact moment.

How do I show the validation error next to the input in a Blade form? Use @error('birthday') in your Blade template. It renders the error message inline. Laravel’s $errors bag is automatically available in all views when using Form Request validation.

Can this rule be applied to an array of date fields? Yes. Use Laravel’s array dot notation: 'dates.*.birthday' => 'required|date|before:today'. The rule applies to every birthday value in the dates array.