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:

PropertyPurpose
$tableThe database table this Model reads from and writes to
$primaryKeyThe column used to uniquely identify a row (used by find(), update(), delete())
$allowedFieldsA 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.

Note: The primary key column itself and any columns you manage internally (like an 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:

MethodWhat 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
Rule of thumb: stick to Query Builder methods (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.
Next: A Model is only as good as the database connection behind it. Continue with Database Configuration to see how CodeIgniter connects to MySQL and how to keep credentials out of version control.