Custom 404 error pages with CodeIgniter

Quick and simple way to use a standard controller method for 404 error pages.

Create controller/method for 404 errors

system/application/controllers/error.php

<?php
class Error extends Controller {
 
	function error_404()
	{
		echo "404 - not found";
	}
}

Add MY_Router.php

system/application/libraries/MY_Router.php

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
 
class MY_Router extends CI_Router {
 
	var $error_controller = 'error';
	var $error_method_404 = 'error_404';
 
    function My_Router()
    {
        parent::CI_Router();
    }
 
	// this is just the same method as in Router.php, with show_404() replaced by $this->error_404();
	function _validate_request($segments)
	{
		// Does the requested controller exist in the root folder?
		if (file_exists(APPPATH.'controllers/'.$segments[0].EXT))
		{
			return $segments;
		}
 
		// Is the controller in a sub-folder?
		if (is_dir(APPPATH.'controllers/'.$segments[0]))
		{		
			// Set the directory and remove it from the segment array
			$this->set_directory($segments[0]);
			$segments = array_slice($segments, 1);
 
			if (count($segments) > 0)
			{
				// Does the requested controller exist in the sub-folder?
				if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$segments[0].EXT))
				{
					return $this->error_404();
				}
			}
			else
			{
				$this->set_class($this->default_controller);
				$this->set_method('index');
 
				// Does the default controller exist in the sub-folder?
				if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$this->default_controller.EXT))
				{
					$this->directory = '';
					return array();
				}
			}
 
			return $segments;
		}
 
		// Can't find the requested controller...
		return $this->error_404();
	}
 
	function error_404()
	{
		$segments = array();
		$segments[] = $this->error_controller;
		$segments[] = $this->error_method_404;
		return $segments;
	}
 
	function fetch_class()
	{
		// if method doesn't exist in class, change
		// class to error and method to error_404
		$this->check_method();
 
		return $this->class;
	}
 
	function check_method()
	{
		$class = $this->class;
		if (class_exists($class))
		{
			if ($class == 'doc')
			{
				return;
			}
 
			if (! in_array('_remap', array_map('strtolower', get_class_methods($class)))  // don't check controllers using _remap() 
				&& ! in_array(strtolower($this->method), array_map('strtolower', get_class_methods($class))))
			{
				$this->class = $this->error_controller;
				$this->method = $this->error_method_404;
				include(APPPATH.'controllers/'.$this->fetch_directory().$this->error_controller.EXT);
			}
		}
	}	
}
 
/* End of file MY_Router.php */
/* Location: ./system/application/libraries/MY_Router.php */
 

Feedback

.. but CI already has a 404 template? " 404 Page Not Found The page you requested was not found. "
bjorn
February 21, 2009
#1
Yes CI has already, but this custom router let you use your own methods and views for the 404 pages. CI won't allow many things in 404 pages and that's really annoying, so this lib is really useful, thanks for sharing !
Chris
February 21, 2009
#2
FYI: Been down this path before. This doesn't catch the 404's when a method is not available in a controller. 404's from methods are handed off to another lib via the CI core (hint: exceptions) :)
solidcode
March 1, 2009
#3
Solidcode: that's what check_method() does.
Jérôme Jaglale
March 1, 2009
#4
thank you! very good!!
John an
March 11, 2009
#5
Hi, Great script, very handy. Just wondering if it can be modified for error_db and the other error pages in the "errors" directory? Would be very handy indeed. Thanks again, Rob.
Rob
March 22, 2009
#6
Hello, thanks by source code....
FUEL SIRPA MAMANI
March 30, 2009
#7
hello, big big big big Thanks for sharing this ROUTER method for CUSTOM 404. Because having default - means mostly everybody knows you are using CI. Thanks !
huglester
April 8, 2009
#8
Thanks for sharing. But how can i make it work with you internationalization librairies? If i put a language in the URL that is not present, the error 404 does not work...
Snap
April 19, 2009
#9
Default behaviour of the i18n library: when a language is not found in the URL, redirect to $default_uri (at least, that's what was supposed to happen. There was a bug, that I just fixed). If you want to generate a 404 error instead: in MY_Language.php, in the constructor MY_LAnguage(), just remove the "else" statement (and the code in it).
Jérôme Jaglale
April 20, 2009
#10
Vraiment enorme ! merci
Hilton
May 26, 2009
#11
I added a line to your IF clause in the check_method() function. Otherwise, if someone is using _remap() in a controller, this will override it and throw an error. Here's my updated version:
function check_method()
	{
		$class = $this->class;
		if (class_exists($class))
		{
			if (! in_array('_remap', array_map('strtolower', get_class_methods($class)))                     // <-- Allows me to use the _remap function in my controllers without throwing a 404 error 
                && ! in_array(strtolower($this->method), array_map('strtolower', get_class_methods($class))))
			{
				$this->class = $this->error_controller;
				$this->method = $this->error_method_404;
				include(APPPATH.'controllers/'.$this->fetch_directory().$this->error_controller.EXT);
			}
		}
	}
Chris
May 28, 2009
#12
Thank you very much Chris, I updated the code.
Jérôme Jaglale
May 28, 2009
#13
I am having some issues with this 404 setup, any help? Example... http://lakesideohio.com/about (valid controller, page works) http://lakesideohio.com/about/nope (CI error) http://lakesideohio.com/about/main (valid controller, page works) http://lakesideohio.com/about/main/nope (multiple CI errors, 'main' is the default controller)
Rob
May 29, 2009
#14
wow it rocks man!! so easy and well working!! GREAT THANKS!!!!!!!!!!!!!!!!1
wow
June 26, 2009
#15
Sorry guys I'm new to CI :-( Can anyone explain to me what to do after creating these two files and puttin them into their locations?? I've simply copied and pasted the two snippest above and created the two files but the error page stays the same CI Template!! Any help is greatly appreciated
Samara
June 30, 2009
#16
It's awesome! Thank you very much!
GiN
August 2, 2009
#17
So, does this still send a 404 header or just redirect to the error controller? Would I want to send my own 404 header in this case? Thanks.
Jim
August 12, 2009
#18
Yes, you'd want to send your own:
$this->output->set_status_header('404');
echo "404 - not found";
Jérôme Jaglale
August 12, 2009
#19
Thanks, Jérôme! One more question: How do I make this work with the show_404('page') method (http://codeigniter.com/user_guide/general/errors.html)? Do I need to extend CI_Exceptions?
Jim
August 17, 2009
#20
Thanks for this, http://dzineblog.com/2008/11/custom-error-404-pages.html here is some link for nice custom error 404 pages.
Zoran
August 21, 2009
#21
Hi Jerome, thank you for the great class! I'm trying to trigger the 404 page without redirecting from within a controller but don't understand how to get it to work: I'm using custom routes with a regular expression -> $route['stuff/([a-z_-]+$)'] = "stuff/viewByType/$1"; if the visitor types the url "mysite.com/stuff/noexists" I need to trigger the 404. For now I can only get it to work by using "redirect('error/error_404', 'location');" and for usability this is not good as it changes the url (yes i'm picky^^)... Any idea on this? Thanks for your time and great libraries
Erik
September 6, 2009
#22
Ok, nevermind just found a way :) May not be the correct way to do it but it works : if('condition is false') { MY_router::_validate_request(NULL); }
Erik
September 6, 2009
#23
Well, no I jumped the gun... my question still holds :)
Erik
September 6, 2009
#24
Erik, you could use the code that you have in error_404() in your "stuff" controller (when there's a 404). If you don't like the code duplication, you could then externalize that code in a helper or library.
Jérôme Jaglale
September 8, 2009
#25
Thank you for your response Jérôme ! I'm fairly new to frameworks so making a helper or library is not (yet) an option for me and as you guessed I don't like code duplication (which is one of the reasons why I came to CI in the first place). I finally opted for an error method in my controller as you suggested. Once again thank you for your help and great libraries :)
Erik
September 12, 2009
#26
Thank for your contribution, but to accomplish same, I have simply placed a 'header('Location: http://example.com/error/404')' in CI default error_404 template
KSE
September 16, 2009
#27
Thank you KSE, but the point here is to not change the URL.
Jérôme Jaglale
September 16, 2009
#28
Nice. The standard CI 404's are indeed rather limited in functions so this is a good solution
Steven Dobbelaere
September 29, 2009
#29
Very nice. Works like a charm. I will be sure to comment here if I find any bugs!
Sotta
October 28, 2009
#30
hi, i just create the two files and follow the instruction to where it should be placed. i don't know, why it does not take effect when i try to browse a not existing page on my site. it just displays the default error page of CI. Please need some help. :(
Rhiane Caña
October 29, 2009
#31
Thanks for the blog... Great help... :) i got an idea because of your blog, i just integrate some of your codes to CI Router... and it works... Thanks a lot.... :)
Rhiane Caña
October 30, 2009
#32
Thanks Jérôme for the code, however I think for my current situation KSE's solution is best. I will most likely use in other projects though!
Nick
November 7, 2009
#33
Thank you Nick, but a problem with KSE's solution is that you don't let your visitor know what is the wrong URL. And you can't log it either.
Jérôme Jaglale
November 7, 2009
#34
Jérôme, We know it is a 404 error, and we could get the URL that they tried to reach. Thus really we could create a link that looked like this: http://example.com/error/404/badurl.com along with any other information... I am now more curious on if this is a good way to do it, or would this be considered bad practice. My thing is, it seems like a lot less code this way, one bad side is you have one more page request... Thoughts?
Nick
November 8, 2009
#35
Jérôme, just wanted to say thank you, thank you, thank you! This class works perfectly and is exactly what I needed. I was preparing to write one myself and dreading the thought. Thanks again!!
fildawg
November 13, 2009
#36
I was sometimes getting "Cannot redeclare class Error" errors, so I wrapped the Error controller with "if (!class_exists('Error')) { }" and she's all sweet now mate, hey. :)
James from Oz
November 16, 2009
#37
Works great, however how would you make this compatible with the show_404() function? You've hinted at it above but can you give me an example of how to do it because i'm stumped.
Steve
November 23, 2009
#38
Great script. Worked instantly with no messing around. Top work.
Paul
November 25, 2009
#39
Agree with @Paul
klanjabrik
November 25, 2009
#40
Great script, thank you!
Summer
December 2, 2009
#41
If you want your new, beautiful custom 404 page to show up when there are disallowed characters in the URL, check out this thread: http://codeigniter.com/forums/viewthread/50154/
Summer
December 6, 2009
#42
THANK YOU Jérôme Jaglale!!! THAT LINE IS EXTREMELLY IMPORTANT BECAUSE IF YOU HAVE A CUSTOM 404 THAT RETURNS 200 IT IS BAD!!! REAL BAD!!!

December 12, 2009
#43
Thanks for this. One correction: You need to reset the directory to the directory where your error handler class is - in case you encounter the error inside a subdir. If you do not the error class will not be found function error_404() { $this->directory=""; $segments = array(); $segments[] = $this->error_controller; $segments[] = $this->error_method_404; return $segments; }
Ben
December 13, 2009
#44
And of course also in the check_method() set $this->directory="" ..
Ben
December 14, 2009
#45
Deprecated: Assigning the return value of new by reference is deprecated in C:\xampp\htdocs\ci-ext\system\codeigniter\Common.php on line 123 Deprecated: Assigning the return value of new by reference is deprecated in C:\xampp\htdocs\ci-ext\system\codeigniter\Common.php on line 129 i m getting thease error please help:-(
siddesh
December 15, 2009
#46
why thats complicated way? without framework it is a 5 lines of code :)
oh
December 20, 2009
#47
good work, thanks =). CI needs more users like you, since its own staff isn't doing much to improve it =/ Sometimes I can't believe it still doesn't have native features like the one you added here Thanks again
horape
January 16, 2010
#48
i love CI :)
master
January 20, 2010
#49
Wow! This is already amazingly helpful! I will be using this script on every CI project from now on! Thanks!
Matt
January 27, 2010
#50
Hi! Big thanks for this. Do you have a similar solution for handling other error types as well. DB, PHP & General ? Thanks a lot!
Jay
January 31, 2010
#51
An Error Was Encountered

Unable to load your default controller. Please make sure the controller specified in your Routes.php file is valid.

I seem to be getting that error when having my controller in a sub directory and I tried setting $this->directory = ''; but then I receive an error for any include file that has been used.
Topher
February 15, 2010
#52
So I fooled around with this a little and by adding $this->directory = '' to error_404() before the return, Errors do not get sent to an error page inside a subfolder containing a controller, but rather to the main error controller. Hope this makes sense and helps. =) cheers

Topher
February 15, 2010
#53
Nice! Thank you.
bie
February 16, 2010
#54