Code Igniter

Code Igniter is a simple and easy-to-learn PHP framework following the Model-View-Controller (MVC) architectural pattern.

The best way to get started is to use composer:

$ composer create-project codeigniter4/appstarter my_project
$ cd my_project && php spark serve # test that it works

Some features are:

  • πŸ›£οΈ URI Routing
  • πŸ”οΈ Built-in security and input validation helper
  • πŸͺ️ Built-in session management helper
  • πŸš€οΈ Built-in caching helper
  • πŸ”€ Localization

Code Igniter 4 is the current major version.

File structure

Code Igniter files are split into 3 sections: app, public, and writable.

➑️ Don't forget to rename env to .env. Set CI_ENVIRONMENT to development to load debugging features.


A modern secure practice is to not expose the website source (app...) to the public. Your website domain name such as should be configured to point to /path/to/my_project/public/. A user won't be able to write

☠️ Traditionally, pointed to /path/to/my_project/ that allowed us to do

You will store in public any file that the user will be allowed to access using a URL. This includes:

  • πŸ–ΌοΈ Images (ex: public/assets/img/)
  • πŸ–ŒοΈ CSS (ex: public/assets/css/)
  • πŸ€– JavaScript (ex: public/assets/js/)
  • ...

The path to this folder is stored in PUBLICPATH from the code.


This directory contains files created by the application:

  • πŸ“„ Logs (debug logs, errors in production)
  • πŸš€ Cache files (delete one to clear its page cache)
  • 🧡 Session files (each user created session)
  • πŸ§‘ Uploaded files (any uploaded file that the user shouldn't access)

The path to this folder is stored in WRITEPATH from the code.


The application folder is split into more folders.


The most important files are:

  • App.php - edit $baseURL to match your domain name; or the path to the public folder during development

  • Routes.php - map a route (ex: /) to a controller (ex: Home.php)


A controller is a PHP script that can render multiple views of your application. We usually create one controller per set of related views.

<?php // Home.php
namespace App\Controllers;

class Home extends BaseController {
    public function index(): string
        return view('welcome_message');


This folder contains PHP files that contain or generate the HTML that will be displayed to the user.

<?php // welcome_message.php
echo "Hello, World!";

Controllers πŸ€–

A controller is a PHP class that extends BaseController. Each method corresponds to a page of your website. They should fetch data from the model, parse it, and pass it to the views.

For instance, imagine a page that displays the latest post on a website.

  • we will first fetch the latest post (Model)
  • then we will display it (View)
public function index(): void {
    // Model
    // imagine that we load this from the model
    $latest_post = ['title' => '...', 'content' => '...'];
    // View
    echo view('header.php');
    echo view('latest.php', [ 'post' => $latest_post]);
    echo view('footer.php');

The view(path) method will execute the PHP in app/Views/path. We can pass variables to these PHP files. When executing latest.php, it will have access to the variable $post which was set to $latest_post.

You may redirect the user:

public function index(): ?\CodeIgniter\HTTP\RedirectResponse
    if ($is_connected) {
        // ...
        return null; // no redirect
    // see also: redirect()->to("/xxx")
    return redirect('login'); // redirect

You may raise a throw PageNotFoundException::forPageNotFound("message"); exception if


Views are simply PHP files with the HTML to display.

if (!isset($post)) die('Missing required argument: $post');

<h1>Latest Post</h1>

<p>Title: <?=$post['title']?></p>
<p>Content: <?=$post['content']?></p>

You will most likely use:

  • site_url('XXX'):, for links
  • base_url('XXX'):, for images

πŸ›£οΈ Routing πŸ›£οΈ

By default, with file routing, you'll have URLs such as and one PHP per route (/toto.php).

Code Igniter uses automatic routing. Every request is redirected to public/index.php using the .htaccess file.

Then, using app/Config/Routes.php and app/Config/Routing.php, it will call a method from a Controller according to the routes we defined.

Auto Controller Routing

Before, code igniter was enabling auto-routing, meaning that would call method from Controller. This is insecure, don't use it.

// app/Config/Routing.php
public bool $autoRoute = false;

Default routing

You can edit these two to change the default controller/method used when there is none specified in the routes defined later.

// app/Config/Routing.php
public string $defaultNamespace = 'App\Controllers';
public string $defaultController = 'Home';
public string $defaultMethod = 'index';

Specify routing patterns

Routes are declared in app/Config/Routes.php:

$routes->get('/', 'XXXController::index');

➑️ It means $base_url/ will call XXXController#index().

You can pass arguments to your method:

$routes->get('/', 'XXXController::index/arg1/arg2');

You can create dynamic routes:

// the first argument will be the number matching (:num)
$routes->get('/products/:num', 'Products::show_product/$1');
// the first argument (:any) is optional
$routes->get('/xxx/(:any)?', 'XXX::xxx/$1');

➑️ You can replace get(...) with post, put, delete...

404 page

If a route is not found, this controller/method will be called if set.

// app/Config/Routing.php
public ?string $override404 = 'App\Controllers\XXX::yyy';

⚠️ Don't forget to set the HTTP response code to 404 in yyy...


Define the database configuration in apps/Config/Database.php. To avoid writing SQL, Code Igniter has what we call Models πŸš€.

$db = \Config\Database::connect();

To create a non-prepared statement ❌ (vulnerable):

$user_id = "5"; 
$r $db->query("SELECT * from user where id = ".$user_id." LIMIT 0,1;");

To create a prepared statement βœ…:

$user_id = "";
$stmt = $db->prepare(function($db) {
    $sql = "SELECT * from user where id = ? LIMIT 0,1;";
    return (new \CodeIgniter\Database\Query($db))->setQuery($sql);
$r = $stmt->execute($user_id);

To get the results as an array:

$results = $r->getResultArray();

While it's not a good practice, you can use the driver-specific execute and write code specific to mysqli/...

$res = $stmt->_execute([$user_id]);
// use the driver-specific functions


Code Igniter implements a sort of Object-Relational Mapping (ORM) using Models. Each model corresponds to a table in the database.

namespace App\Models;

use CodeIgniter\Model;

class PostModel extends Model
    protected $table = 'posts';
    protected $primaryKey = 'id';
    protected $allowedFields = ['id', 'title', 'content'];
    protected $validationRules = [
        'title' => 'required|min_length[3]',
        'content' => 'required'
// crΓ©ate
$postModel = new \App\Models\PostModel();         // normal
$postModel = model(\App\Models\PostModel::class); // shared

// Select id,title,content from posts;
$posts = $postModel->findAll();

// Select [...] from posts where id = some_id;
$post = $postModel->find('some_id');
// Select [...] where id in ['id_1', 'id_2', 'id_3']
$posts = $postModel->find(['id_1', 'id_2', 'id_3']);

// Select [...] from posts where key = value;
$posts = $postModel->where('key', 'value')->findAll();

// Update every record
$postModel->update(['key' => 'value']);
// you can also use "set"
$postModel->whereIn('id', [1, 2, 3])
    ->set(['banned' => 1])


From any file, you can access the request (the data about the one requesting the page), and the response (the response to the request).


From a Controller, you can use:


Request: file upload

You can use Code Igniter file utilities to handle uploads.

$f = $this->request->getFile("fileID"); // null?
$file_name = $f->getName(); // empty?
$size = $f->getSize(); // 1000000=1mb
$ext = $f->guessExtension(); // ex: 'png'
$mtype = $f->getMimeType(); // ex: 'image/png'
$f->store("folderName", "fileName");


To create apps supporting multiple languages, you need to create folders per locale (en, fr...) in app/Language.

Edit app/Config/App.php to set available locales and the default one.

public string $defaultLocale = 'en';
public bool $negotiateLocale = false;
public array $supportedLocales = ['en'];
// don't fallback to default if the locale is not found

For instance, let's say you have the file en/Test.php.

return [
    // key => value
    'greeting' => 'Hello'

Inside the code, instead of a hard-coded text, use:


You'll create the same file in another language (ex: fr/Test.php):

return [
    'greeting' => 'Bonjour'

Now, according to the locale loaded, the text will change.

  • Use {locale} in routes (en/home, fr/home)
  • Auto-detection if negotiateLocale is true
  • From the code
// Get the locale
$locale = service('request')->getLocale();
// Set the locale

➑️ See localization for more details.



You can define the default mail configuration in apps/Config/Email.php. Here is a list of methods you can use:

$email = \Config\Services::email();;
$email->setFrom('', 'XXX xxx');
$email->setTo(''); // one
$email->setTo(','); // multiple
$email->setSubject('[XXX] XXX');
    <meta http-equiv='Content-Type' content='text/html; charset=utf-8' />


Using the throttler, you can enforce a limit/quota on webpages, such as only allowing an IP to access a page every X hours.

$throttler = Services::throttler();
// 10 times per day
if ($throttler->check("key", 10, DAY) === false) {
    // more than 10 this day
    return ...;

Each time check is called with key, the counter increases by one. You could create a key made of an IP to enforce a quota per client.


To log a message with a log level:

log_message('info', 'message');
log_message('error', 'message');

πŸ‘» To-do πŸ‘»

Stuff that I found, but never read/used yet.

  • .env
  • Autoload
  • Helpers
  • Session
  • Cache pages. Annoying in debug.
// you can use Controller#attribute