Python Flask Web Development: Flask Templates, Jinja2 Tutorial, Bootstrap Integration, and Custom Error Pages

jinja2 templates in flask

Python Flask Web Development: Flask Templates, Jinja2 Tutorial, Bootstrap Integration, and Custom Error Pages

Welcome to the Python Flask Web Development series! If you’re new, you can go to the very first post in this series over here to start from the very beginning.

So far in this series, you’ve learned how to write a simple Hello World application and be able to run it from your terminal. In this section, I’m going to cover the Jinja2 Template Engine, and how to use Flask templates.

[sociallocker id=”605″]

Download Example Code for this Post (Templates, etc.)


Bonus: Download a list of 67 Programming Side Project Ideas to put your Flask knowledge to the test. The best way to learn is by hacking away at a project, so while you follow along this series, be sure to have an idea of what you want to build. 

Why Templates are Important

In our previous post, we went over and built a small Hello World application which returned a simple string to the browser (if you haven’t seen it, watch it here). Now in general, having strings directly programmed into your view functions isn’t the best practice. We want to write code that is easy to maintain, and modularized in terms of what each piece of code does. In Flask however, our view functions serve two independent purposes which may lead some beginner programmers to write unmaintainable code.

  1. A view function’s purpose is to generate a response to a request, like we saw in our Hello World application. We saw that our view function had returned a string, that we were able to see visually in our browser.
  2. The second purpose of a view function is that each request from the client triggers a change in state of the application.

These two purposes of the view functions may lead you to mix “business logic” with “presentation logic”.

Business Logic vs. Presentation Logic

Business Logic in your application often has to do with how your program objects are modeled and interact with each other, and how your data is stored and retrieved. For example, in a banking application let’s say we have the ‘account’ object and a ‘user’ object. The logic in your program that defines whether that user has access to a specific account would be the “business logic” of your application.

Presentation Logic on the other hand has to do with how we display objects and data to our user. This would be how the user-interface behaves according to the state of the application, and how the overall application works in terms of the view. An example would be how the interface behaves, colors, the width of the forms, etc. That’s why we use Templates in Flask to contain the “Presentation Logic” of our application.

Overview of Templates

Template: A template is typically a text file (in our case we will be using HTML files) that contain text which is going to be the response we will be sending to our users. These Templates contain placeholder variables which allow us to have dynamic parts in our file, which are then filled with the Jinja2 Template Engine. The process of these variables getting filled in with actual values is called rendering. After the html file is rendered, it will then be sent as a response to the client. We can remember that Jinja2’s role in this process is to render our templates.

How Flask Templates are Rendered using the render_template() Function

So how do you exactly use templates? By default, Flask looks for templates in a ‘Templates” subfolder in your application’s root directory. Let’s do a very simple example using our Hello World application, and modify it a little bit. Make sure you do create a “Templates” folder in your application’s root directory for your index.html file.

As you can see from the code, we are using the ‘render_template()’ function. This function is responsible for taking in a filename as its first argument and then rendering it using Jinja2’s template engine. You can also add additional arguments to this function which will then be used as values for placeholder variables in our templates. Here’s another example where you can see us passing in additional arguments.

When you run ‘’, you can see that the text data we had in our index() function “sam01”, was rendered in our index.html file. The ‘username=username’ argument that is passed into our render_template() function might look a little bit confusing so I’ll break it down so you understand exactly what is going on there. The right side of the keyword argument represents the variable in our current scope which will be used as the value we’re going to pass into our flask template. The left side of the keyword argument on the other hand represents the “name” that will be used as the placeholder in the template we are rendering.

Jinja2 Templates: Variables

As you saw in our previous example, we used ‘{{ username }}’ as a placeholder that tells Jinja2 to fill in a value here when the template is being rendered. This data will come from our view function, in this case we passed in the string “sam01” into our template, and as a result, Jinja2 fills that placeholder with the “sam01” string. Variables aren’t the only thing Jinja2 can handle, we can use dictionaries, lists, objects, and other complex types of data. In our next example we can see how to use other data structures with Jinja2.

Jinja2 Templates: Control Structures

Jinja2 also allows us to use control structures in our templates. This allows us to be able to build templates that can have conditional statements, which helps us modify our views according to certain conditions. This includes if-else statements, and loops. Loops are commonly used to render lists of elements. Our next example is going to show you how to use control structures in our templates.

Template Inheritance and Block Tags

Now that we’ve covered some of the basics of Jinja2, I want to introduce to you the concept of Template Inheritance. Template Inheritance is a way we can reuse the templates we write. Basically what it allows us to do is build a base template that has all the common elements of a page. It also lets you define all the blocks that your child templates can override. The reason Jinja2 provides us this feature is so that we can extend our templates to write cleaner code. Constantly copying and pasting certain parts of your page like the navigation bar to each template file can make it very difficult for you to go back later and edit a certain element of that navigation bar (as an example). This can lead you to have unmaintainable code, and can cause bugs later down the road. Here is a simple diagram to visualize how you can think about template inheritance.

Let’s also look at an example to see it in action.

The first template we create is the base template, name the file “base.html” and save it in your “Templates” folder. Block tags have been added as well, and their role is to define elements that the “child” pages can change/override. A block tag basically just tells the template engine that a child template can override those placeholders, and fill it in with the content from their own template. In our example we can see that the “base.html” template has three defined blocks. The head, title, and body blocks are all used as place holders in this template, and the child template is then going to override these placeholders with it’s own content. It’s very important that you understand what block tags are because we will be using them extensively in our templates.

Bootstrap Integration with Flask using Flask-Bootstrap Extension

So far in this series, I haven’t really talked too much about Flask extensions. Back in our very first lesson, I went over how Flask was designed to be extendable and that it didn’t really come with a lot of built in features as opposed to Django. This meant we had more control over our application, however that also means we need to go out and find the right extensions to use in our Flask application. The first Flask extension I want to introduce is the Flask-Bootstrap extension which allows us to use the Bootstrap library in our application. If you don’t know what Bootstrap is, it’s basically a front-end HTML/CSS/JS framework that comes in with a bunch of CSS classes and Javascript functions. It basically makes your web pages look very well designed, without having to reinvent the wheel. So for our Flask application, we are going to install the Flask-Bootstrap extension onto our machine. I want to point out the reason I chose to use the extension is so that Bootstrap is integrated with our application in a lot smoother way. Instead of having to directly integrate Bootstrap in our templates, we can simply install the extension and keep our code clean. Because I also am introducing Flask extensions in this series for the first time, I want to point out that the way we install Flask extensions is to use pip. You can also check out the documentation for the extension over here.

To install Flask-Bootstrap, enter the following command in your terminal:

Flask extensions also need to be initialized in our application and are usually done so at the same instance of when the application instance is created. In our case, Flask-Bootstrap is initialized in line 5, and is imported in line 2 looking at the example application below. Once we have Flask-Bootstrap initialized, we now have a base template that we can use that includes all the Bootstrap files we need in our application, which is why we have our template extending a “base.html” file from Bootstrap. This is another example of Template Inheritance being used, and this basically allows us to have a parent page that includes all the Bootstrap CSS and Javascript files. The diagram below is a visualization of how the templates are related to each other in this example.

We can actually improve this example a lot more, and in order to do that, we are going to create a ‘base.html’ file, but this time it will be the “child template” of Bootstrap’s ‘base.html’ file. The template relationships in this example can be visualized with the diagram below.

The Flask-Bootstrap extension’s base.html file also comes with a lot of predefined blocks that you can use in your child templates. Here is a list of all the blocks you can use.

Bonus: Download a list of 67 Programming Side Project Ideas to start applying your Flask knowledge right now. 

Flask-Bootstrap’s Base Template Blocks

Block nameOuter BlockPurpose
docOutermost block.
htmldocContains the complete content of the <html> tag.
html_attribsdocAttributes for the HTML tag.
headdocContains the complete content of the <head> tag.
bodydocContains the complete content of the <body> tag.
body_attribsbodyAttributes for the Body Tag.
titleheadContains the complete content of the <title> tag.
stylesheadContains all CSS style <link> tags inside head.
metasheadContains all <meta> tags inside head.
navbarbodyAn empty block directly above content.
contentbodyConvenience block inside the body. Put stuff here.
scriptsbodyContains all <script> tags at the end of the body.

One thing to note is that some of the blocks on this list are used by the extension itself, so overriding a block in a child template you write can cause problems. To get around this issue, what you have to do is use the ‘super()’ function that Jinja2 provides in order to override a block that the extension has a dependency on. A possible scenario would be that if you wanted to add additional scripts to your application you would be overriding the ‘scripts’ block, which is a block that the extension uses. The example below shows you a snippet of code that you can write in your template to avoid problems with your code.

Building out Custom Error Pages

In your Flask application, whenever you or the user enter in an invalid route into your browser’s address, you’re going to get a 404 error. This is where you can come in and build out custom error handlers in your Flask application to handle errors like these when they occur. Flask gives you the option to create custom error pages for your application which can based off your base templates so you can give your error pages the same look of your application. Error pages work just like regular routes, however they only show up when an error gets triggered. The default error page we get is way too plain and doesn’t really fit our application, so let’s go ahead and create our error page.

The error handler in our application (just like our view functions), returns a response which is our template but it also returns a status code as well. The diagram below shows a visualization of our example application in terms of template inheritance.

Now as an exercise, you can go ahead and create multiple error pages for your application.

Interlinking Pages Together using url_for()

Most likely your application is going to have multiple pages, and those pages are going be linked together. One thing you can do is manually link together your templates by writing in your URLs directly, however it gets much more complicated when you throw in dynamic links into the mix. Also, writing in your URLs directly into your templates can cause all sorts of dependencies, and can overall lead your code to be extremely rigid, and if you reorganize your routes, links in your templates can break. This is why Flask comes in with a helper function called ‘url_for()’ which is able to generate URLs based off your application’s URL map. The ‘url_for()’ function takes in the view function name as an argument and returns the view function’s URL. In our example, if we call url_for(‘index’), we will get back ‘/’.

Relative vs. Absolute URLs

To discuss the ‘url_for’ function in more detail, I want to talk about Relative and Absolute URLs in our applications. If we simply call ‘url_for(‘index’)’ in our example application, it will return a Relative URL, meaning that the URL generated doesn’t explicitly specify any sort of protocol (like “http://” or “https://”). This “forces” the web browser to assume that this URL is on the same site, and for us that means that the Relative URL returned was derived from the different routes of our application. Absolute URLs on the other hand are for links that need to be used outside of the web browser, such as when you’re linking to another website. An easy way to remember Relative URLs is that these URLs are “relative” to our application, as in they are good enough to link together pages within our application. I have also provided additional links down below incase this section was a little bit confusing. If you don’t understand the difference, don’t worry too much about it right now.

Dynamic Routes

We can also use the url_for() function to generate dynamic URLs. An example of a dynamic URL being generated would be, url_for(‘channel’, username=’h3h3productions’, _external=True), which would return the URL in a case where you create a view function called “channel” in a quick example app on your machine. The example below shows us how we can create dynamic routes.

Static Files in our Application

Our web applications will also include more than just template files. We will also be using static files, which includes our CSS, Javascript, and image files which we can reference from our HTML templates. Flask by default looks into a folder called static in our application’s root directory, where we can organize all our static files. The example below shows a very simple example of adding an image to a webpage. Be sure to create a folder called ‘static’ in your application’s root directory, and add any image into that folder for the example below to be replicable.

Also here is an image of how the directory structure looks like:

flask static files

Additional Links


That wraps up everything I wanted to cover about Flask templates, I really hope you got a lot out of this write-up, and be sure to play around with all the example code above. There’s a lot you can do with the things that I have gone over in this post, so make sure you build out a little web application to further solidify your understanding of templates in Flask. Be sure to leave a comment, and share this post if you found it resourceful!

You can also check out the previous post here!

3 Responses

Leave a Reply

Follow Us 🐦