Laravel Validation- depends on the value of another input field
Stefan Bogdanescu
Founder & Senior Architect · 2026-06-29
Mastering Conditional Validation in Laravel: Validating Based on Other Fields
As senior developers working with Laravel, we frequently encounter scenarios where data validation isn't a simple one-to-one mapping. Often, the validity of one field depends entirely on the value provided in another—this is known as conditional validation. Trying to achieve this using standard, static rules often leads to brittle, complex code that is hard to maintain and debug.
Today, we are going to tackle a common challenge: validating releaseYear and productionYear based on the selected status. We will explore the best ways to implement this dynamic logic in Laravel, ensuring our application remains robust and clean.
The Challenge: Dynamic Cross-Field Validation
Let's define the requirements for our scenario:
- If
statusis 'Released':productionYearandreleaseYearmust not be greater than the current year. - If
statusis 'UnReleased':productionYearmust not be greater than one year from the current year.
The initial validation you provided:
$request->validate([
'productionYear' => 'nullable|digits:4',
'releaseYear' => 'required|digits:4|after_or_equal:year_of_production', // Note: This reference is currently invalid in a standalone validation context.
'status' => 'required|in:Released,UnReleased',
]);
This setup handles basic rules but fails to implement the conditional dependency. We need logic that reads the value of status and dynamically changes the rules applied to the year fields.
Solution 1: Implementing Conditional Rules via Closures
For complex, interdependent validation, the most powerful and idiomatic Laravel approach is to use Closure-based validation rules. This allows you to define a rule that executes custom logic based on the entire request data set.
We can achieve our goals by defining a single, comprehensive rule within the rules() method of your Request class or controller, or by using an explicit validation block where we check the status first.
Here is how you can structure this logic:
public function rules()
{
$rules = [
'productionYear' => 'nullable|digits:4',
'releaseYear' => 'required|digits:4',
'status' => 'required|in:Released,UnReleased',
];
// Apply conditional logic based on the status field
if ($this->input('status') === 'Released') {
// If Released, years must be less than or equal to the current year (2024 in this example)
$rules['productionYear'] = '<=:' . now()->year;
$rules['releaseYear'] = '<=' . now()->year;
} elseif ($this->input('status') === 'UnReleased') {
// If UnReleased, production year must be within the next year
$rules['productionYear'] = '<=' . (now()->year + 1);
}
return $rules;
}
Developer Insight: While this approach works perfectly for basic scenarios, defining complex logic directly in the validation rules can make the Request class overly verbose. For very large applications, developers often prefer separating heavy business logic out of the validation layer and into a dedicated Form Request or service layer. This keeps your validation clean and adheres to the Single Responsibility Principle, which is a core tenet of good Laravel architecture, much like following best practices outlined on laravelcompany.com.
Solution 2: Controller-Level Logic (The Robust Alternative)
For extremely intricate dependencies, sometimes performing the validation in the controller after initial checks provides better separation of concerns. This is especially useful when the rules become too complex for simple closure syntax.
- Perform Initial Validation: Validate the fields as required (e.g., ensuring they are digits).
- Check Status and Apply Business Logic: Use standard PHP
if/elsestatements to enforce the specific business constraints based on the validated data.
public function store(Request $request)
{
$validated = $request->validate([
'productionYear' => 'nullable|digits:4',
'releaseYear' => 'required|digits:4',
'status' => 'required|in:Released,UnReleased',
]);
$status = $validated['status'];
$productionYear = $validated['productionYear'];
$releaseYear = $validated['releaseYear'];
if ($status === 'Released') {
// Validation for Released status
$currentYear = now()->year;
if ($productionYear > $currentYear || $releaseYear > $currentYear) {
throw new \Illuminate\Http\Exceptions\HttpResponseException(
response()->json(['message' => 'Years cannot be greater than the current year for Released status.'])
);
}
} elseif ($status === 'UnReleased') {
// Validation for UnReleased status
$maxProductionYear = now()->year + 1;
if ($productionYear > $maxProductionYear) {
throw new \Illuminate\Http\Exceptions\HttpResponseException(
response()->json(['message' => 'Production year cannot exceed one year from the current date for UnReleased status.'])
);
}
}
// If all checks pass, proceed with saving data...
// ...
}
Conclusion
When dealing with conditional validation in Laravel, there isn't a single magic rule; instead, developers must choose the architectural pattern that best fits the complexity of the logic. For moderately complex rules, Closure-based validation offers a concise solution within the Request layer. However, for highly intricate business rules that involve comparing multiple fields across different states, moving the complex decision-making into the Controller or a dedicated Service Layer provides superior readability and maintainability. Always aim for clear separation of concerns, which is fundamental to building scalable applications, whether you are using Laravel or leveraging other frameworks like those discussed on laravelcompany.com.