Layout and Template Customization

Everything works, but if you go to /register... it looks awful. Well, of course it looks awful! FOSUserBundle has no idea how the page should be styled. But don't worry: we can get this looking much better, very quickly.

First, on the web debug toolbar, find the template icon and click it. This will show you all the templates used to render this page... which is a beautiful cheat sheet for knowing what templates you can override!

Correcting the Base Layout

The one I'm interested in is layout.html.twig, which lives in FOSUserBundle.

In my editor, I'll press Shift+Shift to open that file. Ok, every Twig template in FOSUserBundle extends this layout.html.twig file. For example, see the "Logged in as" text? That's coming from here.

But, we want all of FOSUserBundle's templates to instead extend our base.html.twig template. How can we do that?

Overriding the Layout

By overriding layout.html.twig. Let's see how. First, to override any template from a bundle, just go to app/Resources, then create a directory with the same name as the bundle: FOSUserBundle. Inside, create one more directory: views.

Tip

The location where templates should live to override bundle templates has changed in Symfony 4. But, the idea is still the same. For details, see: https://symfony.com/blog/new-in-symfony-3-4-improved-the-overriding-of-templates.

In this case, the layout.html.twig template lives right the root of the views/ directory in the bundle. So that's where we need to create our's. Inside, extend the normal base.html.twig.

{% extends 'base.html.twig' %}
... lines 2 - 6

Here's the magic part. Hit Shift+Shift again and open register.html.twig: this is the template for the registration page. Notice that it overrides a block called fos_user_content. In layout.html.twig, this is printed in the middle.

So check this out: inside of our version of layout.html.twig, add {% block body %}: that's the name of the block in our base.html.twig. Then, include {% block fos_user_content %} and {% endblock %}.

{% extends 'base.html.twig' %}
{% block body %}
{% block fos_user_content %}{% endblock %}
{% endblock %}

We're effectively transferring the content from the fos_user_content block to the correct block: body.

These 5 lines of code are huge. Refresh the page! Ha! So much better! Not perfect, but every page now lives in our layout. If you want, you can even add a little more markup around the block.

... lines 1 - 2
{% block body %}
<div class="container">
<div class="row">
<div class="col-xs-12">
{% block fos_user_content %}{% endblock %}
</div>
</div>
</div>
{% endblock %}

Overriding Individual Templates

Overriding the base layout is step one. But, each individual page still won't look quite right. On this page, we at least need a "Register" h1, and I'd like to make that button look better.

So in addition to overriding layout.html.twig, you really need to override every template from FOSUserBundle that you use - like registration, reset password, login and a few others.

Once again, click the templates link in the web debug toolbar. The template behind this page is register.html.twig, which we already have open. But notice, it immediately includes register_content.html.twig. This is a really common pattern in this bundle.

Let me show you: I'll click the views link to move my tree into FOSUserBundle. In Registration, we have register.html.twig and register_content.html.twig. In Profile there's the same for edit and show.

In most cases, you'll want to override the _content.html.twig template. Why? Well, it doesn't really matter: by overriding the _content.html.twig template, you don't need to worry about extending anything: you can just focus on the content.

Copy the contents of register_content.html.twig. Then, back in app/Resources/views, create a Registration directory. I'm doing that because this template lives in a Registration directory. Finally, create register_content.html.twig and paste in the content. Let's add a couple of classes to the button and an h1 that says: "Register Aquanauts!"

{% trans_default_domain 'FOSUserBundle' %}
<h1>Register Aquanaut!</h1>
{{ form_start(form, {'method': 'post', 'action': path('fos_user_registration_register'), 'attr': {'class': 'fos_user_registration_register'}}) }}
{{ form_widget(form) }}
<div>
<input class="btn btn-primary" type="submit" value="{{ 'registration.submit'|trans }}" />
</div>
{{ form_end(form) }}

Ok, refresh! Love it! In your app, make sure to do this for all of the different pages from the bundle that you're using. And remember, if you don't need a page - like the edit profile page - save yourself some time by not importing its route or overriding its template.

Leave a comment!

  • 2018-09-25 Jaime Stuardo

    Hello... I have finally solved by making the framework to render only that hidden field. I used simply {{ form_widget(form._token) }}. in twig template.

  • 2018-09-25 Victor Bocharsky

    Hey Jaime,

    Hm, probably because you're passing invalid value to csrf_token()... why "authenticate"? It's not an authentication, i.e. login form. If we're talking about ResettingFormType - it should be "resetting", see https://github.com/FriendsO... . I think it should help

    Or you can use form_start()/form_end() Twig functions that will render opening and closing form tags and also this hidden csrf token field. But to avoid rendering fields you can call {% do form.plainPassword.setRendered %} before {{ form_end(form) }} call to mark this field as rendered and render it manually with your layout as you do.

    Cheers!

  • 2018-09-24 Jaime Stuardo

    I have overriden fos_user_resetting_form with my own... When I did it, I am receiving "Invalid CSRF token" error. I tried to add this to the form: <input type="hidden" id="fos_user_resetting_form__token" name="fos_user_resetting_form[_token]" value="{{ csrf_token('authenticate') }}"/> but it didn't help.

    I am customizing the whole form, so I am not using form_widget(form) function call.

    This is the whole form:


    <form name="fos_user_resetting_form" method="post" action="{{ path('fos_user_resetting_reset', {'token': token}) }}" class="needs-validation" novalidate="">
    <input type="hidden" id="fos_user_resetting_form__token" name="fos_user_resetting_form[_token]" value="{{ csrf_token('authenticate') }}"/>
    <div class="form-group">
    <label for="fos_user_resetting_form_plainPassword_first" class="required">{{ 'form.password'|trans }}</label>
    <div class="input-group">
    <div class="input-group-addon"></div>
    <input type="password" class="form-control" id="fos_user_resetting_form_plainPassword_first" name="fos_user_resetting_form[plainPassword][first]" required="required" autocomplete="new-password"/>
    <div class="invalid-tooltip">Por favor, ingrese la nueva contraseña.</div>
    </div>
    </div>
    <div class="form-group">
    <label for="fos_user_resetting_form_plainPassword_second" class="required">{{ 'form.password_confirmation'|trans }}</label>
    <div class="input-group">
    <div class="input-group-addon"></div>
    <input type="password" class="form-control" id="fos_user_resetting_form_plainPassword_second" name="fos_user_resetting_form[plainPassword][second]" required="required" autocomplete="new-password"/>
    <div class="invalid-tooltip" id="confirm_password_error">Por favor, ingrese la nueva contraseña.</div>
    </div>
    </div>
    <div>
    <button type="submit" id="_submit" name="_submit" class="btn btn-success btn-flat m-b-30 m-t-30">{{ 'resetting.reset.submit'|trans }}</button>
    </div>
    </form>

    Any ideas? Thanks.
    Jaime

  • 2018-06-18 Влад Юрчук

    project/templates/bundles/FOSUserBundle/Security/login_content.html.twig
    project/templates/bundles/FOSUserBundle/layout.html.twig

  • 2018-05-03 Diego Aguiar

    Hey Mohamed Djawadou-Dine

    If you are following this tutorial but using Symfony4, you will find some differences about the structure (that's because Symfony4 comes with many structural changes). Probably watching our tutorials about Symfony4 before can give you a better understanding about the changes, and will enable you to overcome the differences found in this tutorial.
    Symfony4 tuts: https://knpuniversity.com/t...

    Cheers!

  • 2018-05-03 Diego Aguiar

    Hey Mohamed Djawadou-Dine

    Can you tell me what's actually your problme? Are you trying to override any FOSUserBundle template on Symfony4? If that's the case, check this link: https://symfony.com/blog/ne...

    Cheers!

  • 2018-05-03 Mohamed Djawadou-Dine

    hi have you alreday to it i have the same probleme .Thks
    i begin alredy my projet before add fosuserbundle so i'm confisued

  • 2018-05-03 Mohamed Djawadou-Dine

    Hi evryone
    Can this tutoriel gonna work with SF4.If yes how i my gona to redirect directories.

    Thanks again Ryan & the team for your tutorials :)

  • 2018-03-16 Victor Bocharsky

    Haha, would be easier... but look less awesome? ;) Well, we have to follow the business, you know :)

    Cheers!

  • 2018-03-16 Céline Ollagnier

    Thanks Victor for your answer. It was what I was afraid of >.< Next time no modals at allllllll ;)

  • 2018-03-15 Victor Bocharsky

    Hi Céline,

    First of all, you need to render those forms in that bootstrap's modal. If you look inside FOSUserBundle, you'll find templates. For example, "FOSUserBundle/Resources/views/Security/login.html.twig" renders the login form but it also extends base layout with valid HTML, i.e. html, head, body tags. So, you don't need it if you want to embed your form into model window. Look closer inside login.html.twig template, it also includes another template "@FOSUser/Security/login_content.html.twig" which actually renders this form. And that's exactly what you need. Try to include this form inside your modal. That was the 1st step.

    The second step is to make this form to be submitted via AJAX, here you need to write some custom JS to intercept submit button pressing and send form via AJAX instead.

    And the 3rd step: you need to detect that the form was submitted by AJAX and return JsonResponse instead of redirect / html response. For it, you will need to create a custom success / failure authenticator handler and point security component to use them. Then in your custom JS you'll need to handle that JSON response and close popup, or refresh the page, or redirect to another page.

    But yeah, that's quite tricky and that sucks, because this way you need to override a lot of code :/ but probably the another solution is just to create all it manually, for example with Guard authenticator which is pretty flexible. In case you're interested in it, here's a screencast: https://knpuniversity.com/s...

    Cheers!

  • 2018-03-14 Céline Ollagnier

    Hi everyone.
    Our web designer has created a landing page with a modal for connection and another for registration (bootstrap 3), she used them a lot... I'm beginning with Fos, so I don't really know how to procede to link them correctly.
    Thanks again Ryan & the team for your tutorials :)

  • 2018-01-31 weaverryan

    Hey kaizoku!

    Sorry for the slow reply - Disqus put your comment in Spam! Rude! And yea, you're right, in Symfony 4, the location for putting your templates for overriding has changed. I'll add a ticket to add a note about this in the tutorial :).

    Cheers!

  • 2018-01-27 kaizoku

    Hi guys, this isn't working anymore for SF4.
    Check this link to override templates : https://symfony.com/blog/ne...

    Cheers,

  • 2017-09-06 Victor Bocharsky

    Hey Pascal,

    I think you can, create a new action, e.g. "/login-or-register", where create login and register forms, then render them in your own template. I think it should work out of the box as far as its URLs in form action attribute refers to the proper FOSUserBundle controllers, but probably if your form invalid - user will be on its specific form page. Otherwise, you need to handle those submitted forms manually, though you can steal ready code from FOSUserBundle but to do so probably it will be harder to upgrade FOSUserBundle in the future.

    Cheers!

  • 2017-09-05 Pascal Q (pascal.)

    How can I use the login form in another template (ie include that login form to my own template which includes both register and login in one template) ?

  • 2017-08-11 mattxtlm

    Hey Diego Aguiar,

    that solved the issue totally. My translator in config.yml was deactivated. That could have been quite a long search, because I had no idea where to start.

    Thanks a lot!

    Matt

    PS: I already tried to override the translation files, but that didn't have any effect. Now I know why :-)

  • 2017-08-11 Diego Aguiar

    Hey mattxtlm

    Those labels are made for beign translated, FOSUserBundle comes with translations for a lot of languages, but you have to activate it, like this:


    // config.yml
    framework:
    ....
    translator: { fallback: en }


    if you give a look into the default form templates, you can see `{% trans_default_domain 'FOSUserBundle' %}` that's like the namespace where the FOS translations lives, if you would like to override them, you will have to create a translation file: `app/Resources/translations/FOSUserBundle.{language}.yml` and replace the key you want, but don't forget you need to clear cache whenever you create a new translation file (if you only update it, dont have to)

    I hope it helps you, cheers!

  • 2017-08-11 mattxtlm

    Somehow, my FOS forms (the customized and the non-customized) display labels like "form.email" "form.password", "form.submit". It seems to me, some variables can't be resolved, but I don't know where to look at.

    Oh, and in my FOS Flash Messages and emails, that's also the case.

    Thanks,
    Matt

  • 2017-08-09 Diego Aguiar

    Hey kaizoku!

    If you are going to only override the templates, there are other easier ways to do it. I would suggest you to create a child bundle only when you don't have any other choice (maintaining extra bundles represents more work)

    Have a nice day :)

  • 2017-08-09 kaizoku

    It isn't recommended anymore to declare our Userbundle as child bundle of FOS in order to override template ?