CodeIgniter Models & Query Builder
A Model is the "M" in MVC — the layer of your application responsible for talking
to the database. In CodeIgniter 4, a Model is a plain PHP class that extends the framework's base
Model class, gaining a large set of ready-made methods for free. Underneath, CodeIgniter
also exposes a Query Builder: a fluent, chainable API for constructing SQL without
hand-writing raw query strings.
1. Defining a Model Class
Model classes live in app/Models/ and are named after what they represent, using
PascalCase with a Model suffix — for example ArticleModel for a table of
articles. A minimal Model looks like this:
<?php
namespace App\Models;
use CodeIgniter\Model;
class ArticleModel extends Model
{
protected $table = 'articles';
protected $primaryKey = 'id';
protected $allowedFields = ['title', 'content', 'status'];
}
That's a complete, usable Model. It doesn't need a constructor, a database connection, or any query
methods written by hand — extending Model already wires all of that up for you.
2. The Key Configuration Properties
The three protected properties shown above are the ones you'll set on almost every Model:
| Property | Purpose |
|---|---|
$table | The database table this Model reads from and writes to |
$primaryKey | The column used to uniquely identify a row (used by find(), update(), delete()) |
$allowedFields | A whitelist of columns that insert() and update() are allowed to write to |
$allowedFields deserves special attention. Without it, a Model would happily insert or
update any key found in the array you hand it — including keys an attacker slipped into a
submitted form, such as is_admin or role. This is called a
mass-assignment vulnerability. By explicitly listing only the columns that are safe
to set from user input, CodeIgniter silently drops anything not on the list, so a malicious extra
field in a POST request can never reach a column it shouldn't.
is_admin flag) should never appear in $allowedFields — that keeps them impossible to overwrite via mass assignment, even by accident.3. Built-in Methods You Get for Free
Because ArticleModel extends CodeIgniter's base Model, it automatically
inherits a full set of CRUD methods — no need to write a single line of SQL for the common cases:
| Method | What it does |
|---|---|
find($id) | Returns one row matching the primary key, or null |
findAll() | Returns every row in the table as an array |
insert($data) | Inserts a new row from an associative array of column => value |
update($id, $data) | Updates the row matching $id with the given data |
delete($id) | Deletes the row matching $id |
$model = new ArticleModel();
// Read
$article = $model->find(5);
$all = $model->findAll();
// Create
$newId = $model->insert(['title' => 'Hello', 'content' => 'First post']);
// Update
$model->update(5, ['title' => 'Updated title']);
// Delete
$model->delete(5);
4. The Query Builder
For anything beyond simple lookups by primary key, CodeIgniter provides the Query
Builder — a fluent, method-chaining API for building SQL statements piece by piece. Models
expose it directly, since a Model is a pre-configured Query Builder aimed at its own table.
You can also grab a builder aimed at any table directly from the database connection with
$this->db->table('articles'). Common building-block methods include
where(), orWhere(), orderBy(), limit(), and
join().
$builder = $this->db->table('articles');
$published = $builder->where('status', 'published')
->orderBy('created_at', 'DESC')
->get()
->getResultArray();
Reading this chain left to right almost reads like English: build against the articles
table, filter to rows where status = 'published', orderBy the newest first,
get() executes the query and returns a Result object, and
getResultArray() converts that result into a plain array of associative arrays. The same
chain works directly on a Model instance, since Models proxy Query Builder calls to their own table:
$model = new ArticleModel();
$published = $model->where('status', 'published')
->orderBy('created_at', 'DESC')
->findAll();
When called on a Model, findAll() plays the role of get()->getResultArray()
combined — it runs the built-up query and hands back a plain array, so most day-to-day queries never
need to touch $this->db directly at all.
5. Automatic Protection Against SQL Injection
Every value you pass into Query Builder methods like where() is automatically sent to
the database as a bound parameter, not concatenated directly into the SQL string.
This means a value such as ' OR '1'='1 is treated purely as data — a literal string to
compare against — and can never change the shape or meaning of the query, which is exactly the trick
classic SQL injection relies on.
// The Query Builder turns this into a parameterized query automatically
$model->where('email', $userInput)->first();
// Behind the scenes, roughly equivalent to a prepared statement:
// SELECT * FROM articles WHERE email = ? -- bound, never concatenated
where(), insert(), update(), and friends) instead of building raw SQL strings by hand, and you get injection protection without having to think about it. If you ever do need raw SQL, use $this->db->query('...', [$bindings]) so your values still go through as bound parameters.