How to upgrade CodeIgniter 3 to 4: a step-by-step migration guide (and when to choose Laravel)
How to upgrade CodeIgniter 3 to 4: what changed in routing, models, validation and sessions, a step-by-step plan, and when Laravel is the better move.
If you are working out how to upgrade CodeIgniter 3 to 4, start with this: the move is a rewrite of the framework layer, not an upgrade. CodeIgniter 4 is not backwards compatible, there is no tool that converts an app for you, and the official advice is to start a fresh CI4 project and move your code into it piece by piece. The good news is that your database, your business rules and most of your SQL survive. What changes is everything around them: folder structure, namespaces, routing, models, validation, sessions and how classes load.
This guide walks through the real differences, a step-by-step migration plan I would follow on a live app, and the cases where moving to Laravel instead makes more sense. If you would rather hand the move to someone who has done it, that is what my CodeIgniter 3 to 4 migration service covers. It is checked against the official CodeIgniter 3.x to 4.x upgrade guide (opens in a new tab) and the CI4 4.7 documentation as of October 2026.
Quick answer
- Why move: CodeIgniter 3.1.13 (March 2022) is the last official CI3 release. The project is in maintenance and does not officially support PHP 8.2 or newer.
- Target: CodeIgniter 4.7 requires PHP 8.2+ with the
intlandmbstringextensions. - Method: new CI4 skeleton, copy config, then convert routes, controllers, models, views and libraries one module at a time, with tests on each.
- Data: the database stays. Two tables need attention:
ci_sessions(recreate) andmigrations(new definition). - Alternative: if the app is large, still growing and short on tests, a move to Laravel can cost about the same effort and leave you on a bigger ecosystem.
Why CodeIgniter 3 apps are a risk in 2026
CodeIgniter 3 still runs a lot of business software, including Perfex CRM, which I customize regularly. The problem is the ground underneath it. The last official CI3 tag, 3.1.13, shipped on March 3, 2022. The repository README says CI3 "is in maintenance, receiving mostly just security updates", and the develop branch that would become 3.2.0 has never been released.
Meanwhile PHP keeps moving. According to php.net (opens in a new tab), PHP 8.1 is already out of security support, PHP 8.2 gets its last security fix on December 31, 2026, and hosts are pushing accounts to 8.3 and 8.4. On PHP 8.2+, an untouched CI3 app logs "Creation of dynamic property" deprecations on every request, and PHP 8.4 and 8.5 add more. I cover those errors and the patches in detail in CodeIgniter 3 on PHP 8: errors and fixes. Patching buys time. It does not give you a supported framework.
Signs your app is due:
- Your host has announced a forced PHP upgrade, or you are pinned to an old PHP version on purpose.
- Logs fill with deprecation notices you have learned to ignore.
- New developers struggle with
$this->loadmagic and theMY_core overrides. - You want Composer packages, tests or a modern auth library and CI3 fights you.
CodeIgniter 3 vs 4: what actually changed
The headline of any CodeIgniter 3 vs 4 comparison is that CI4 was rebuilt from scratch on modern PHP. Here are the differences that cost time during a migration.
| Area | CodeIgniter 3 | CodeIgniter 4 |
|---|---|---|
| PHP | 5.6+ recommended, officially up to 8.1, 8.2+ only with patches | 8.2+ (CI4 4.7), intl and mbstring required |
| Structure | application/, system/, index.php in the root |
app/, system/, writable/, public/index.php |
| Namespaces | None, global classes | PSR-4, App\ and CodeIgniter\ |
| Loading | $this->load->... super object |
Services, model(), helper(), new |
| Routing | $route[...] array plus auto-routing |
$routes->get() etc., auto-routing off by default |
| Models | extends CI_Model, raw query builder |
extends Model with $table, $allowedFields, find(), insert() |
| Query builder | snake_case: order_by, result_array() |
camelCase: orderBy, getResultArray() |
| Validation | form_validation library, callbacks |
validateData(), rule classes, closure and callable rules |
| Sessions | $this->session->userdata() |
session()->get(), new DB table schema |
| Extending core | application/core/MY_*.php |
Replace via app/Config/Services.php |
| Hooks | config/hooks.php |
Events and controller Filters |
| Migrations | Sequential or timestamp names | Timestamp only, php spark migrate |
A few libraries are gone entirely: Calendaring, FTP, Javascript, Shopping Cart, Trackback, XML-RPC and Zip Encoding. The CAPTCHA, Email, Path and Smiley helpers were removed too, and the Download helper is replaced by the Response object's download() method. If your app depends on the Cart library, budget time to rebuild it, because there is no drop-in replacement.
How to upgrade CodeIgniter 3 to 4: the step-by-step plan
This is the order I follow. It keeps the old app running until the new one has proven itself.
1. Audit and freeze the scope
List every controller, model, library, helper, hook and MY_ class. Note which removed libraries you use, every form validation callback, and every place that touches $_SESSION, $_POST or $_GET directly. Count the routes and export your current URL list from the access logs. This list becomes your test checklist and your estimate.
Freeze new features in the CI3 app for the duration, or at least agree that every new feature gets built twice. Parallel development is the main reason migrations drag on.
2. Set up the CI4 skeleton and server
Start clean with Composer:
composer create-project codeigniter4/appstarter my-app
cd my-app
cp env .env
php spark serve
Point the web server's document root at public/, not the project root. Set CI_ENVIRONMENT = development in .env locally, configure the database in .env or app/Config/Database.php, and make writable/ writable by the web server user. Confirm the production server has PHP 8.2 or newer with intl and mbstring before you write any app code.
3. Move configuration
CI4 configuration lives in classes under app/Config that extend CodeIgniter\Config\BaseConfig. Your old $config['site_name'] entries become public properties:
<?php
namespace Config;
use CodeIgniter\Config\BaseConfig;
class Site extends BaseConfig
{
public string $siteName = 'Acme Portal';
public string $siteEmail = 'support@example.com';
}
Read it with config('Site')->siteName instead of $this->config->item('site_name'). Put secrets (database password, API keys) in .env, not in the class.
4. Convert routing
Auto-routing is off by default in CI4, and I recommend keeping it off. Defining routes explicitly is safer and doubles as documentation. CI4 also offers "Auto Routing (Improved)" if you have hundreds of URLs and need a bridge.
// CodeIgniter 3: application/config/routes.php
$route['orders/(:num)'] = 'orders/show/$1';
$route['product/(:any)'] = 'catalog/product_lookup';
// CodeIgniter 4: app/Config/Routes.php
$routes->get('orders/(:num)', 'Orders::show/$1');
$routes->get('product/(:segment)', 'Catalog::productLookup');
Watch the placeholders: in CI4, (:any) matches across multiple segments, so CI3's single-segment (:any) usually becomes (:segment). Use HTTP-verb routes (get, post) rather than add(), which the docs keep only for backward compatibility.
5. Convert controllers and views
Each controller gets a namespace and extends BaseController. The defined('BASEPATH') line goes, because CI4 does not define BASEPATH. Views are returned instead of loaded:
<?php
namespace App\Controllers;
use App\Models\OrderModel;
use CodeIgniter\Exceptions\PageNotFoundException;
class Orders extends BaseController
{
public function show(int $id)
{
$order = model(OrderModel::class)->find($id);
if ($order === null) {
throw PageNotFoundException::forPageNotFound();
}
return view('orders/show', ['order' => $order]);
}
}
$this->input->post('email') becomes $this->request->getPost('email'). One trap: redirect() no longer stops execution. It returns a RedirectResponse, so redirect('login') must become return redirect()->to('login');, or the code after it keeps running.
6. Convert models and the query builder
Move models to app/Models, add the namespace and extend CodeIgniter\Model. Declaring $table and $allowedFields gives you find(), insert(), update() and delete() for free, and $allowedFields protects you from mass assignment.
<?php
namespace App\Models;
use CodeIgniter\Model;
class OrderModel extends Model
{
protected $table = 'orders';
protected $primaryKey = 'id';
protected $returnType = 'array';
protected $allowedFields = ['customer_id', 'status', 'total'];
public function paid(): array
{
return $this->where('status', 'paid')
->orderBy('id', 'DESC')
->findAll();
}
}
For hand-written queries elsewhere, the query builder is close to CI3 but camelCase, and it starts from a table:
// CodeIgniter 3
$rows = $this->db->select('id, total')
->where('status', 'paid')
->order_by('id', 'DESC')
->get('orders')
->result_array();
// CodeIgniter 4
$rows = db_connect()->table('orders')
->select('id, total')
->where('status', 'paid')
->orderBy('id', 'DESC')
->get()
->getResultArray();
Two behavior changes to test for: CI3's query caching is gone (use the Cache library), and limit(0) returns all rows in CI4 instead of none.
7. Convert validation
The form_validation library and its run() method are replaced by validateData() in the controller. CI3-style callback_ rules are not supported, so each callback becomes a rule class, a closure rule (4.3+) or a callable rule (4.5+).
public function store()
{
$data = $this->request->getPost(['email', 'phone']);
if (! $this->validateData($data, [
'email' => 'required|valid_email',
'phone' => 'required|numeric|max_length[10]',
])) {
return redirect()->back()->withInput();
}
$clean = $this->validator->getValidated();
// save $clean ...
return redirect()->to('/thanks');
}
In views, validation_errors() becomes validation_list_errors(). Note that CI4 validation never changes your data, so a trim rule no longer trims the value you save. Trim explicitly.
8. Convert sessions, auth and libraries
$this->session->userdata('user_id') becomes session('user_id') or session()->get('user_id'), and set_userdata() becomes session()->set(). If you use the database session driver, the table definition changed, so recreate it from the CI4 docs. Everyone will be logged out at cutover. Plan for that rather than trying to carry sessions across.
Your own libraries move to app/Libraries (or any namespace) and load with new instead of $this->load->library(). Hooks become Events, and pre_controller or post_controller hooks become Filters, which are also where auth checks belong. If you stored values encrypted with the CI3 Encryption library, the CI4 docs describe a compatibility configuration. Test decryption of real production samples before cutover.
9. Database migrations
If you used CI3 migrations, rename sequential files to the timestamp scheme, move them to app/Database/Migrations, extend CodeIgniter\Database\Migration and switch Forge calls to camelCase (addField, addKey, createTable). The migrations table definition also changed, so create it fresh with CI4 and move the history over before the first php spark migrate on production.
10. Test, run in parallel, cut over
Click through the URL list from step 1, run your tests, and compare outputs (invoices, reports, exports) between the old and new app against a copy of production data. Then cut over with the old release ready to restore.
Does an automatic CI3 to CI4 converter exist?
No official one. The community package kenjis/ci3-to-4-upgrade-helper (opens in a new tab) provides CI3-style compatibility classes so you can convert CodeIgniter 3 to 4 faster, but its README says plainly that it "does not aim to be 100% compatible" and that it is under early development. I treat it as scaffolding: useful to get a large app running on CI4 quickly, then removed module by module. If you lean on it permanently, you have a CI3 app wearing a CI4 costume.
Migrating a live app without a big-bang switch
For a busy app, the safest pattern is a strangler-fig migration: the new CI4 app takes over one URL area at a time while CI3 serves the rest. Both apps share the same database. The web server routes by path prefix, for example /reports to CI4 and everything else to CI3. Each area moves, gets tested in production with real traffic, and then the next one follows.
The hard part is shared login. CI3 and CI4 sessions are not compatible, so you need either a single sign-on bridge (a signed token handed from one app to the other) or a cutover where auth moves first. This adds work, so I only recommend it when downtime or risk is expensive. For an internal tool with a few dozen users, a planned weekend cutover is simpler.
Keep URLs identical wherever you can. If public pages change paths, map every old URL to its new one with 301 redirects, and check Search Console after launch. That part is covered in my SEO website migration service.
When moving to Laravel is the better choice
Because a CodeIgniter 3 to 4 upgrade rewrites the framework layer anyway, it is fair to ask whether that rewrite should target Laravel. I compare the frameworks in general in Laravel vs CodeIgniter. For migrations specifically, this is how I decide:
| Choose CodeIgniter 4 when | Choose Laravel when |
|---|---|
| The app is stable and mostly in maintenance | The app is still growing with new features every month |
| Your team knows CI and wants the same mental model | You need queues, scheduled jobs, auth scaffolding, API tokens out of the box |
| Hosting is basic shared hosting | You want a larger hiring pool and package ecosystem |
| Budget is tight and the goal is "supported PHP again" | The code needs restructuring anyway (fat controllers, no models) |
If you decide to migrate CodeIgniter to Laravel, the pieces map fairly directly: controllers to controllers, CI models to Eloquent models, views to Blade, form_validation to Form Requests, hooks to middleware, cron scripts to the scheduler. Bcrypt hashes created with PHP's password_hash() and PASSWORD_DEFAULT keep working with Laravel's default hasher. Custom hashing schemes can be rehashed at the next login. If you are weighing that route, my CodeIgniter to Laravel migration page explains how I plan it in phases.
What drives the effort
I won't give a universal timeline, because the spread is huge. These are the factors that decide it:
- Number of controllers and routes. Each one is converted and tested.
- Removed libraries (Cart, XML-RPC, FTP) and removed helpers you depend on.
- Core overrides in
application/coreand hooks. These are the riskiest pieces. - Validation callbacks, because each one becomes a rule class or closure.
- Business logic in controllers or views. Fat controllers are slower to move.
- Tests. Most CI3 apps have none, so writing a few feature tests for the money paths comes first.
- Third-party integrations (payment gateways, SMS, accounting) that need retesting end to end.
A written audit of these points turns into a fixed quote. If you want me to do that audit or the whole move, see my CodeIgniter 4 migration service.
Frequently asked questions
Can I upgrade CodeIgniter 3 to 4 by replacing the system folder?
No. Replacing the system folder works between CI3 point releases, but CI4 changed the structure, namespaces and APIs. You create a new CI4 project and move your application code into it, adapting each file.
Will my database and URLs stay the same?
Your tables and data stay as they are, apart from the session table and the migrations table, which CI4 defines differently. URLs stay the same if you define routes to match the old ones, which I recommend for every public page.
Can CodeIgniter 3 run on PHP 8.2, 8.3 or 8.4?
Not officially. CI3.1.13 predates PHP 8.2, and on newer versions it raises deprecation notices that you have to patch in the core or work around with a community fork. It is a stopgap while you plan the CodeIgniter 3 to 4 migration or a move to Laravel.
Is CodeIgniter 4 still maintained?
Yes. CodeIgniter 4.7.0 was released on February 1, 2026, and the 4.7.x line continues with point releases. It requires PHP 8.2 or newer.
Should I migrate to CodeIgniter 4 or Laravel?
Choose CodeIgniter 4 if the app is stable and you mainly need a supported framework with the least change. Choose Laravel if the app is still growing or needs queues, scheduled jobs and a wider package ecosystem, since you are rewriting the framework layer either way.
If your CI3 app is getting nervous about the next PHP upgrade, I am happy to look at the code and tell you honestly which path fits, through my CodeIgniter 4 migration service.