CodeIgniter 4 validation not working? 9 causes and fixes
Fix CodeIgniter 4 validation problems fast: wrong input source, lost errors after redirect, permit_empty, strict JSON rules, is_unique placeholders and more.
6 min read

In this article
When CodeIgniter 4 validation "does not work", the library is almost always doing exactly what it was told, just with different data or rules than you thought. The usual culprits are validating the wrong input source, losing errors on redirect, optional fields without permit_empty, strict type checks on JSON, and model validation quietly skipping rules during updates.
Below are nine causes we see again and again, each with the symptom, the reason and the fix. Everything applies to CodeIgniter 4.7 and matches the current user guide; where behaviour changed in an earlier 4.x release, the version is noted.
Quick diagnosis table
| Symptom | Likely cause | Section |
|---|---|---|
| Valid data fails, or invalid data passes | Validating a different input source | 1 |
| Errors never appear in the form | Redirect without withInput() | 2 |
Empty optional field fails valid_email | No permit_empty | 3 |
JSON request fails in_list with a correct-looking value | Strict rules and types | 4 |
| Second run returns old errors | No reset() | 5 |
is_unique fails when saving an unchanged record | Placeholder not replaced | 6 |
required ignored on update | Model cleans rules for absent fields | 7 |
| File rules behave oddly | Mixing general rules with file rules | 8 |
| "Rule not found" exception | Custom rule class not registered | 9 |

1. You are validating different data than you think
Symptom: a form value is clearly there but validation fails, or a field you did not send passes. Cause: the older controller method $this->validate() and the library's withRequest() read input with $request->getVar(), which merges $_GET, $_POST and cookies, and for JSON or PUT requests reads the body instead. The user guide now marks both as backward-compatibility only and warns that a cookie can override a POST value of the same name.
Fix: collect the data yourself and use validateData() (available since 4.2), then read the result with getValidated() (since 4.4), which returns only the fields that had rules:
public function store()
{
$rules = [
'name' => 'required|max_length[100]',
'email' => 'required|max_length[254]|valid_email',
'phone' => 'permit_empty|max_length[20]',
];
$data = $this->request->getPost(array_keys($rules));
if (! $this->validateData($data, $rules)) {
return redirect()->back()->withInput();
}
$valid = $this->validator->getValidated();
model('ContactModel')->insert($valid);
return redirect()->to('/contacts')->with('message', 'Saved');
}For a JSON API, use $this->request->getJSON(true) ?? [] as the data instead of getPost().
2. Errors disappear after a redirect
Symptom: validation fails, the user is sent back to the form, and no messages appear. Cause: PHP shares nothing between requests, so the errors from the failed request are gone. Fix: redirect with redirect()->back()->withInput(), which stores the errors and old input in the session, and display them with the form helper functions validation_list_errors(), validation_show_error('email') or validation_errors(). Load the helper with protected $helpers = ['form']; in the controller, and use set_value() or old() to refill fields.
3. Optional fields fail format rules
Symptom: an empty, optional phone or website field fails with "must contain a valid URL". Cause: format rules do not permit an empty string. Also, a field that is missing from the data is treated as null, not skipped. Fix: start optional fields with permit_empty. It accepts an empty string, null, false or an empty array and skips the remaining rules, except required_with and required_without. If you need to know that a key was sent at all, use field_exists (since 4.5); to validate a key only when it is present, use if_exist.
4. JSON values fail because rules are strict
Symptom: a JSON body with "active": true fails in_list[0,1]. Cause: since 4.3, new projects use the Strict Rules classes, which do not convert types implicitly. Strict in_list accepts strings, integers and floats, but a boolean is neither, so it fails. That is deliberate: the Traditional Rules would turn true into '1' and let it pass, which the user guide calls out as a reason not to use them for JSON.
Fix: agree on the wire format (send 0 or 1), or normalise the value before validating, and keep the strict rules. Do not switch Config\Validation::$ruleSets back to the traditional classes to make a test pass.

5. A second validation run returns old errors
Symptom: in a loop or an import job, every record after the first bad one fails. Cause: the validation service is shared, and run() does not reset error state. Fix: call reset() before each run, and set the rules again, because reset() clears data, rules and custom messages too:
$validation = service('validation');
foreach ($rows as $row) {
$validation->reset();
$validation->setRules($importRules);
if (! $validation->run($row)) {
$failures[] = ['row' => $row, 'errors' => $validation->getErrors()];
}
}6. is_unique fails when editing a record
Symptom: saving a user without changing their email fails with "must contain a unique value". Cause: the rule must ignore the current row, usually with a placeholder: is_unique[users.email,id,{id}]. Since 4.3.5, a placeholder is only replaced if the placeholder field itself has a validation rule and passes it, a change made for security. If id has no rule, or is missing from the data, {id} is never replaced.
Fix: give id a rule and include it in the data being validated. With a model, update($id, $data) validates only $data, so add the key yourself; it is removed again before the query because the primary key is not an allowed field:
protected $validationRules = [
'id' => 'max_length[19]|is_natural_no_zero',
'email' => 'required|max_length[254]|valid_email|is_unique[users.email,id,{id}]',
];Then, in the controller, pass the key along with the changes: $userModel->update($id, ['id' => $id, 'email' => $email]).
Also watch for spaces: rule strings must not contain spaces between parameters, so is_unique[users.email, id, {id}] will not work.
7. required is ignored when a model updates
Symptom: $model->update($id, ['title' => 'New']) succeeds although another field is required. Cause: by default, $cleanValidationRules is true, so on updates the model temporarily removes rules for fields that are not in the data. That makes partial updates possible, but required rules and rules that depend on other fields are not checked. Fix: if an update must be complete, call $model->cleanRules(false)->update($id, $data) or set the property to false. Remember too that in-model validation runs just before saving; anything you do with the data earlier is unvalidated.
8. File rules misbehave
Symptom: an optional avatar field fails, or a bad file passes. Cause: file rules check $_FILES, not the normal data array, and adding general rules such as permit_empty to a file field stops them working correctly. The field name is also repeated inside every file rule: is_image[avatar]. Fix: keep file fields to file rules only (uploaded, max_size, is_image, mime_in, ext_in, max_dims, min_dims), validate with validateData([], $rules), and upgrade to 4.7.5, which fixed two upload validation bypasses, including one in multiple-file fields.
9. A custom rule is not found
Symptom: an exception says your rule does not exist. Cause: rule classes must be listed in Config\Validation::$ruleSets. Fix: add your class, for example \App\Validation\AccountRules::class, next to the framework's rule classes, and make each rule method return a boolean. For one-off checks, the library also accepts closures and callables as rules.
Gotchas and next steps
- Validation never changes data. Trim, cast or normalise before you validate, and save the output of
getValidated(), not the raw request. - Labels make better messages: use
['label' => 'Email address', 'rules' => '...']instead of a bare rule string. - Arrays use dot syntax:
contacts.*.emailvalidates every contact's email. - Escape errors in custom views with
esc(), as the user guide's templates do.
When validation still surprises you, dump three things side by side: the exact array you validated, the rules after placeholders, and $this->validator->getErrors(). In nearly every case one of them is not what you expected, and the fix is a line or two.
Frequently asked questions
Should I use validate() or validateData() in a CodeIgniter 4 controller?
Use
validateData(). The user guide marksvalidate()as backward-compatibility only because it reads input withgetVar(), which mixes GET, POST and cookie values.Why are my validation errors empty after redirect()->back()?
Errors only survive a redirect if you call
withInput(), which stores them in the session. Then show them withvalidation_list_errors()orvalidation_show_error()from the form helper.How do I make a field optional but validated when filled?
Put
permit_emptyfirst, for examplepermit_empty|valid_url_strict. Empty values then skip the other rules, exceptrequired_withandrequired_without.Why does is_unique fail when I update a record?
The rule must exclude the current row with a placeholder such as
is_unique[users.email,id,{id}], and since 4.3.5 theidfield needs its own rule and must be present in the validated data, or the placeholder is not replaced.What is the difference between strict and traditional validation rules?
Strict Rules, the default since 4.3, do not convert types implicitly, so a JSON boolean is not treated as the string
1. Traditional Rules exist only for backward compatibility and should not be used in new projects.Why does my model skip required rules on update?
$cleanValidationRulesdefaults totrue, removing rules for fields absent from the update data. CallcleanRules(false)beforeupdate()when the whole record must be validated.
Your next customer could start right here.
Introduce your tool, product or business to readers exploring technology.
Explore placements and request a private quote
Sources
- Validation — CodeIgniter Foundation
- Controllers: Validating Data — CodeIgniter Foundation
- Using CodeIgniter's Model: In-Model Validation — CodeIgniter Foundation
- Form Helper — CodeIgniter Foundation
- Version 4.7.5 change log — CodeIgniter Foundation, 2026-10-09
Written with substantial AI assistance. Facts checked against cited sources. Editorial policy.
Comments
No comments yet. Be the first to join the conversation.
Leave a comment
Every comment is read by a moderator before it appears. Your email address is never published; it is used only to contact you about this comment.
More from Cloudsoft Zone

Industry updateCodeIgniter
CodeIgniter 4.7.5 security release: what to patch and how
CodeIgniter 4.7.5 fixes six advisories, including a critical upload bypass and Parser code execution. See who is exposed and how to upgrade safely.
By Cloudsoft Zone

Laravel
Upgrading from Laravel 12 to Laravel 13: a practical guide
Laravel 12 lost bug-fix support in August 2026. This guide covers the PHP 8.3 requirement, Composer changes, the CSRF middleware rename, cache and session hardening, low-impact changes that often bite, and a safe rollout plan.
By Cloudsoft Zone

TutorialLaravel
Laravel AI SDK 1.0 tutorial: build your first AI agent in Laravel 13
Build a tested Laravel 13 AI agent with laravel/ai 1.x: providers, a PHP tool, structured output, queued jobs, fakes for tests, cost caps and a security checklist.
By Cloudsoft Zone
