Learn how to include jQuery properly in your Symfony project to prevent the known '$ is not a function' exception

Symfony is one of the best options to work in the backend of your web project and jQuery one of the most easiest frameworks available to manipulate the DOM in the frontend. Without a doubt, for simple websites and blogs this is a good combination, however novice developers of Symfony will face the problem that, when they include jQuery in the base.html.twig file and they write scripts in the child views (in the body block) that extend this view, the known exception $ is not defined will appear in the console.

Note

In this article we won't show what's the best way to include (or locate) the jQuery file in your project, that's up to you. You can get more information about this question by reading this post in Stack Overflow.

Why does this happen?

You have surely included jQuery at the end of the <body> tag of the base document as a good practice to optimize the render time of your page, well done ! However, why then it doesn't works on Symfony properly? To make this error easier to understand, we'll work with examples. We have the following markup as our base.html.twig file in the project:

{# file: base.html.twig #}
<!DOCTYPE html>
<html lang="en">
    <head>
        <title></title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link href="css/style.css" rel="stylesheet">
    </head>
    <body>
        {# Body block where child views will appear #}
        {% block body %}{% endblock %}

        {# 
            Ignoring the way you load jQuery , if you take care of the good practices
            loaded at the end as a good practice 
        #}
        <script src="jquery.js"></script>
    </body>
</html>

The file has only 1 block, the body block. Now, if you are accessing another action in your project that returns some HTML response and uses this base file, the view will look for example like:

{% extends base.html.twig %}

{% block body %}
    <h1>Example Child View 1</h1>

    <p>Some text 1</p>
    
    <script>
        $(document).ready(function(){
            console.log("Script working properly");
        });
    </script>
{% endblock %}

The child view uses JavaScript as you can see and it's very simple. Once you access your route on the browser, the HTML response will be:

<!DOCTYPE html>
<html lang="en">
    <head>
        <title></title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link href="css/style.css" rel="stylesheet">
    </head>
    <body>
        <h1>Example Child View 1</h1>

        <p>Some text 1</p>
        
        <script>
            $(document).ready(function(){
                console.log("Script working properly");
            });
        </script>

        <script src="jquery.js"></script>
    </body>
</html>

Indeed, jQuery is being executed when it's not even available.

Solution

The problem is caused by the missunderstanding of how the Twig blocks work. By default you don't need to load jQuery in the head tag of the document because that will decrease the loading speed of your document. You just need to create a new block namely javascripts (the name can be changed if you want) in your base file:

{# file: base.html.twig #}
<!DOCTYPE html>
<html lang="en">
    <head>
        <title></title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link href="css/style.css" rel="stylesheet">
    </head>
    <body>
        {# Body block where child views will appear #}
        {% block body %}{% endblock %}

        {# 
            Ignoring the way you load jQuery , if you take care of the good practices
            loaded at the end as a good practice 
        #}
        <script src="jquery.js"></script>

        {# 
            Create the javascripts blocks where all the scripts written in the child views
            should appear really
        #}
        {% block javascripts %}{% endblock %}
    </body>
</html>

And if you want to write JavaScript (or load files) in your child views, just write it inside the javascripts block instead of the body:

{% extends base.html.twig %}

{% block body %}
    <h1>Example Child View 2</h1>

    <p>Some text 2</p>
{% endblock %}

{% block javascripts %}
    <script>
        $(document).ready(function(){
            console.log("Script working properly");
        });
    </script>
{% endblock %}

That would generate the following HTML instead:

<!DOCTYPE html>
<html lang="en">
    <head>
        <title></title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link href="css/style.css" rel="stylesheet">
    </head>
    <body>
        <h1>Example Child View 2</h1>

        <p>Some text 2</p>
    

        <script src="jquery.js"></script>
        <script>
            $(document).ready(function(){
                console.log("Script working properly");
            });
        </script>
    </body>
</html>

Which shouldn't throw error anymore because jQuery now exists.

Happy coding !


Senior Software Engineer at Software Medico. Interested in programming since he was 14 years old, Carlos is a self-taught programmer and founder and author of most of the articles at Our Code World.

Sponsors