Routing

Routing is the layer that decides which controller and method handles an incoming URL. In CodeIgniter 4, almost all of that decision-making lives in one file, app/Config/Routes.php, though the framework also offers a convention-based fallback for when you haven't defined a route at all. This page covers both approaches, the placeholder syntax used to capture URL segments, route groups, and named routes.

1. app/Config/Routes.php: the Single Source of Truth

Every explicit route in a CodeIgniter application is registered in app/Config/Routes.php. This file is loaded once per request, and it exposes a $routes object with methods for each HTTP verb. A minimal file looks like this:

<?php

$routes->get('/', 'Home::index');
$routes->get('articles', 'Articles::index');

The first argument is the URL pattern to match (relative to your base URL), and the second is a string in Controller::method form telling CodeIgniter which class and action to call.

2. Automatic (Default) Routing

If a URL doesn't match any route you've defined, CodeIgniter falls back to its automatic routing convention rather than immediately returning a 404. Under this convention, the first URL segment maps to a controller class, the second to a method on that class, and any further segments become arguments passed to that method — positionally, in order.

For example, with zero routes explicitly defined for it, a request to /articles/view/5 is automatically routed to:

App\Controllers\Articles::view(5)

This convention will feel familiar if you've used other MVC frameworks with similar "convention over configuration" defaults. It's convenient for quick prototyping, but most real applications either don't rely on it or actively disable it, because it means any public method on any controller becomes reachable by URL unless you're careful.

Disabling automatic routing: For stricter applications, set $routes->setAutoRoute(false); in Routes.php. Once disabled, a URL only works if it matches an explicit route you've defined — nothing is reachable by convention alone. Most production applications enable this for the extra control and security.

3. Defining Explicit Routes

Explicit routes give you full control: a custom URL pattern, a specific controller/method target, and (via placeholders) the ability to capture parts of the URL as arguments. Here's a route that captures a numeric article ID and passes it straight through to a show() method:

$routes->get('articles/(:num)', 'Articles::show/$1');

(:num) matches the segment and captures it; $1 in the target refers back to that first captured group. You can define routes for any HTTP verb your app needs:

$routes->get('articles', 'Articles::index');
$routes->post('articles', 'Articles::create');
$routes->put('articles/(:num)', 'Articles::update/$1');
$routes->delete('articles/(:num)', 'Articles::delete/$1');

4. Route Placeholders

Placeholders are shorthand patterns that stand in for a segment of the URL and control what characters it's allowed to match. The three you'll use most often are:

PlaceholderMatchesExample URL segment
(:num)One or more digits only5, 1024
(:any)Any character, including additional slashes — greedy, matches the rest of the URLmy/slug/here
(:segment)Any character except a forward slash — exactly one URL segmentmy-article-slug

A common mistake is reaching for (:any) when (:segment) is what's actually wanted — because (:any) will happily swallow extra path segments you didn't intend to match, it's best reserved for routes where that's genuinely the desired behaviour.

5. Route Groups

When several routes share a common URL prefix, or need the same filters (middleware) applied, grouping them keeps Routes.php from repeating itself. $routes->group() takes the shared prefix and a closure containing routes relative to it:

$routes->group('admin', function($routes) {
    $routes->get('dashboard', 'Admin\Dashboard::index');
    $routes->get('articles', 'Admin\Articles::index');
});

Both routes here are actually reachable at admin/dashboard and admin/articles. Groups can also take an options array to apply a filter (such as an authentication check) to every route inside, which is exactly the pattern used to protect an entire admin section in one place — this ties directly into the filters discussed later in Sessions & Security.

6. Named Routes and Generating URLs

Instead of hardcoding a URL string everywhere it's needed, a route can be given a name. Later, you generate the actual URL from that name using the route_to() helper (or, in views, the related url_to() function) — so if the underlying URL pattern ever changes, every place that generated a link from the route's name keeps working without edits.

$routes->get('articles/(:num)', 'Articles::show/$1', ['as' => 'article.show']);
// Elsewhere in your code, this returns the matching URL, e.g. "articles/5"
route_to('article.show', 5);
Next steps: Now that routes can point at any controller/method, head to Controllers to see how a controller receives those captured segments as method parameters and turns them into a response.