Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Customizing Error Pages and How Errors are Handled

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

Customizing Error Pages and How Errors are Handled

Sometimes things fall apart. And when they do, we show our users an error page. Hopefully, a hilarious error page.

Right now, our 404 page isn’t very hilarous, except for the little Pacman ghost that’s screaming “Exception detected”. He’s adorable.

We see this big descriptive error page because we’re in the dev environment, and Symfony wants to help us fix our mistake. In real life, also known as the prod environment, it’s different.

The Real Life: prod Environment

To see our app in its “real life” form, put an app.php after localhost:

We talked about environments and this app.php stuff in episode 1. If you don’t remember it, go back and check it out!

The page might work or it might be broken. That’s because we always need to clear our Symfony cache when going into the prod environment:

php app/console cache:clear --env=prod

Ok, now the site works. Invent a URL to see the 404 page. Ah gross! This error page isn’t hilarous at all! So where is the content for this page actually coming from and how can we make a better experience for our users?

Overriding the Error Template Content

To find out, let’s just search the project! In PHPStorm, I can navigate to vendor/symfony/symfony, right click, then select “Find in Path”. Let’s look for the “An Error Occurred” text.

Ah hah! It points us straight to a file in the core Twig bundle called error.html.twig. Let’s open that up!


The location of the file is:


Cool, so how can we replace this with a template that has unicorns, or pirates or anything better than this?

There’s actually a really neat trick that let’s you override any template from any bundle. All we need to do is create a template with the same name as this in just the right location.

This template lives in TwigBundle and in an Exception directory. Create an app/Resources/TwigBundle/views/Exception/error.html.twig file. Notice how similar the paths are - it’s the magic way to override any template from any bundle.


app/Resources/AnyBundle/views/SomeDir/myTemplate.html.twig will always override @AnyBundle/Resources/views/SomeDir/myTemplate.html.twig

Now just extend the base template and put something awesome inside. I’m going to abuse my login.css file to get this to look ok. I know, I really need to clean up my CSS:

{# app/Resources/TwigBundle/views/Exception/error.html.twig #}
{% extends '::base.html.twig' %}

{% block stylesheets %}
    {{ parent() }}
    <link rel="stylesheet" href="{{ asset('bundles/user/css/login.css') }}" />
{% endblock %}

{% block body %}
    <section class="login">
            <h1>Ah crap!</h1>

            <div>These are not the droids you're looking for...</div>
{% endblock %}

Refresh! Hey, don’t act so surprised to see the same ugly template. We’re in the prod environment, we need to clear our cache after every change:

php app/console cache:clear --env=prod

Refresh again. It’s beautiful. The pain with customizing error templates is that you need to be in the prod environment to see them. And that means you need to remember to clear cache after every change.

Customizing Error Pages by Type (Status Code)

But we have a problem: this template is used for all errors: 404 errors, 500 errors and even the dreaded 418 error!

I think we should at least have one template for 404 errors and another for everything else. Copy the existing template and paste it into a new file called error404.html.twig. That’s the trick, and this works for customizing the error page of any HTTP status code.

We should keep the generic error template, but let’s give it a different message:

{# app/Resources/TwigBundle/views/Exception/error.html.twig #}

{# ... #}
<h1>Ah crap!</h1>

<div>The servers are on fire! Grab a bucket! Send halp!</div>

To see the 404 template, clear your cache and refresh again on an imaginary URL. To see the other template, temporarily throw an exception in EventController::indexAction to cause a 500 error:

// src/Yoda/EventBundle/Controller/EventController.php
// ...

public function indexAction()
    throw new \Exception('Ahhhhahahhhah');
    // ...

Head to the homepage - but with the app.php still in the URL. You should see that the servers are in fact on fire, which I guess is cool. Remove this exception before moving on.

Going Deeper with Exception Handling

Behind the scenes, Symfony dispatches an event whenever an exception happens. We haven’t talked about events yet, but this basically means that if you want, you can be nofitied whenever an exception is thrown anywhere in your code. Why would you do this? You might want to do some extra logging or even completely replace which template is rendered when an error happens.

We won’t cover event listeners in this screencast, but there’s a cookbook called How to Create an Event Listener that covers it.

Normally, when there’s an exception, Symfony calls an internal controller that renders the error template. This class lives in TwigBundle and is called ExceptionController. Let’s open it up!

The class lives at: vendor/symfony/symfony/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php

The guts of this class aren’t too important, but you can see it trying to figure out which template to render in findTemplate. You can even see it looking for the status-code version of the template, like error404.html.twig:

// vendor/symfony/symfony/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php
// ...

$template = new TemplateReference('TwigBundle', 'Exception', $name.$code, $format, 'twig');
if ($this->templateExists($template)) {
    return $template;

I’m making you stare at this class because, if you want, you can actually override this entire controller. If you do that, then your controller function will be called whenever there’s an error and you can render whatever page you want. That process is a bit more involved, but use it if you need to go even further.

Leave a comment!

Login or Register to join the conversation

Hey there, I'm liking a lot this episode.

I has a problem when loading CSS and Images in Prod enviroment. (When I try to browse to "localhost:8000/app.php" )
I got error 404 Not Found.

Clearing cache didn't fix it and then I found this command

# php app/console assetic:dump --env=prod

It did the trick

I hope somebody else find it helpful


1 Reply

Diego! You're totally right - we're using Assetic, but we don't really talk about it until episode 4: https://knpuniversity.com/s...

Thanks for adding the note :)


No problem ;]
I like to help

Peter-K Avatar
Peter-K Avatar Peter-K | posted 3 years ago

This topic requires a full course.

I still dont know what is actually the best way to handle errors or how to work with them from developers point of view.
Does the developer suppose to check log file on production like every day, hour, minutes?

Imagine customer will see error 400 and will send me an email: something is not working... when I wanted to see elephant.
I will probably go to prod.log and see all errors within last few days as he didnt specified time and where exactly he couldnt see elephant.

What I would like to do is on any error. System, permission, 404, 500 json etc saved Error(Exception) in database and show a unique number to customer as reference which he can email to me or automatically I will get an email with that reference.

something like below: "Hi John Snow, something went wrong status code status text we are dealing with your error Err1234321. Sorry for any inconvenience. Email has been sent to us and we are working hard to fix it. We will inform you once its done."

Then I would go to database and see basically exactly the same info like being on dev but have these information in database instead of .log file and storing also username if user was logged.

I believe this would be more beneficial for coders as well as I would be able to filter errors for that user for given time frame and if user would send me a reference number I would see exactly the error number and fix it.

I am reading about ErrorController and how to overwrite but didnt find any real life code or good explanation.

In other words I am looking for Log -> Level Error -> Error Message to be stored in DB with User and sending automatic email to me.


Hey Peter,

I'd add this topic to our possible screencasts list, but unfortunately I can't say when this course might be released.

A few words about it: you probably don't want to get notifications for 4xx errors on production. Because those errors might be too much, and it will just spam you a lot. Basically, 4xx are client errors that means it's client fault to generate them. Like, client is trying to open a page that does not exist, or client just does not have access to the resource, etc. Usually, you want to track only 5xx errors on production, that means something is wrong on server side.

About getting notifications when something is wrong - sure, that would be useful! But it depends on *you*. There's a lot of way to do it: you can get those notifications via Slack, or to your Telegram/Viber channel, or get them by emails, though it's not popular lately thanks to Slack and messengers :) Actually, you even may want to receive notifications not only problems, but also about something important that happened in your application, e.g. you got a new customer that paid you 10 000$ :) And Symfony has a component that would help with it. Check Fabien's talk about Symfony Notifier Component: https://symfonycasts.com/sc... - or read docs about it here: https://symfony.com/doc/cur... . Unfortunately, we don't have a course about it, but we would like to release one some day. Actually, Monolog bundle also may send different alerts, see: https://github.com/Seldaek/...

Except this, there's a lot of interesting built-in handlers in MonologBundle, that may collect logs, handles them and warn your about something bad happen in your application. For example, see Sentry, Rollbar, NewRelic, Loggly handlers: https://github.com/Seldaek/... . And yes, Monolog may log into the DB though I've never done this, see: https://github.com/Seldaek/...

So, yes, you can easily override Symfony error pages, like 4xx / 5xx pages and say something like "We're sorry for this error. We created a ticket ### and our team is already working on it. If you have any questions about it - please, email us and specify this ticket number" something like this. To actually save that information I believe you can use Symfony events and check status of the response. If it's a bad response status - save info to the DB with all the useful into like current user, logs, etc.

There might be some bundle that already may do this job completely or partially, but I don't know any unfortunately.. Try to google it or use GitHub search. Anyway, even if there's no any ready-to-use solution - you are able to write this system yourself for your project.

I hope this helps!


Peter-K Avatar

I end up with using monolog.

It is not perfect at all.

I managed to email and log them into files but Ive noticed URL is not passed.
Different behavior on dev and prod not sure how to set it up.

Not sending user id who has generated this error (if known)

Example is I tried to access undefined variable on dev it will trow 500 exception but on prod it is ignoring this error.

Also not sure how to create unique reference on template.

Not sure how to access for 403 pages with @isGranted permission option for roles like Admin, Superadmin how to display it on 403 error page.

Error would look like "Sorry you need Admin or Superadmin permission to access this page."

I was reading about overriding ErrorController but not sure what else I would break so rather keeping it as it is.

That is just storing errors into file and/or emailing but what about storing errors in DB, I have no idea....

Too many possibilities and documentation is confusing so I would really appreciate a good tutorial with real life example if someone has any


Hey Peter,

Symfony logs some simple default things our of the box... but of course you can easily add your logs :) You can log whatever you want, including the current user. Just use Symfony event for this and add logging of whatever info you think might be useful for you for debugging.


Default user avatar

Hello Ryan!
Hope you are doing good!
In this chapter I've had an issue while I was triying to clear out the cache in order to run the application to the prod enviroment.

Here what I got:

$ ./app/console cache:clear --env=prod

The parameter "stof_doctrine_extensions.uploadable.validate_writable_direct
ory" must be defined.

As you can see I can't access to the production enviroment because of this if I put an "/app.php"

Thanks in advance for your time and help!


Hi Edison!

Hmm, so I've looked at the StofDoctrineExtensionsBundle library, and I'm not sure how this is happening. This parameter should be set for you, even if you do nothing. But, based on what that library is doing, it may just be a one-time issue that you can fix by manually clearing the cache this one time:

rm -rf app/cache/*

Try that, then try the cache:clear command again. I bet this will do it.

Ah, in fact I just found the issue - and the above work-around is "correct": https://github.com/stof/StofDoctrineExtensionsBundle/issues/205

So, not your fault. Fix it and keep going :).


Default user avatar

Hello Again Ryan!
I finally made it on the Error Pages, Thank You! (cache file, it solves the issue)

Now I'm in Epsidoe 4. I installed nodejs binary.exe for OS Windows8 64x, (choosed npm manager package) but it is not working...-bash npm not found...I am using masteruser.

What else should I do....? Thx!!!!!!!!!!!!!!!!!


Hey Edison!

Yes, path issues are always a pain in the butt! Here are a few suggestions:

1) If you're not already, try using a bash other than cmd - like the "git bash" that comes when you install git. This *may* help with your paths, but there's a good chance it won't.

2) With npm, you can install things globally (npm install -g foo) or locally (npm install foo), which installs things into a node_modules directory right in your project. With the global install, your terminal path needs to be setup correctly to know what directory to look in for stuff. But if you instal locally, then you should have a binary located in something like node_modules/.bin/nodejs. Then, you can update your config.yml to point to this path (look at line 9 in the first code-bock here: http://symfony.com/doc/curr....

Let me know if this gets you closer!


Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": ">=5.3.3",
        "symfony/symfony": "~2.4", // v2.4.2
        "doctrine/orm": "~2.2,>=2.2.3", // v2.4.2
        "doctrine/doctrine-bundle": "~1.2", // v1.2.0
        "twig/extensions": "~1.0", // v1.0.1
        "symfony/assetic-bundle": "~2.3", // v2.3.0
        "symfony/swiftmailer-bundle": "~2.3", // v2.3.5
        "symfony/monolog-bundle": "~2.4", // v2.5.0
        "sensio/distribution-bundle": "~2.3", // v2.3.4
        "sensio/framework-extra-bundle": "~3.0", // v3.0.0
        "sensio/generator-bundle": "~2.3", // v2.3.4
        "incenteev/composer-parameter-handler": "~2.0", // v2.1.0
        "doctrine/doctrine-fixtures-bundle": "~2.2.0", // v2.2.0
        "ircmaxell/password-compat": "~1.0.3", // 1.0.3
        "phpunit/phpunit": "~4.1", // 4.1.0
        "stof/doctrine-extensions-bundle": "~1.1.0" // v1.1.0