Flask Cheatsheet

Templates (Jinja)

Use this Flask reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Rendering Templates

from flask import render_template, render_template_string

# Render from templates/ directory
return render_template("index.html", title="Home", user=current_user)

# Render a string directly (useful for dynamic templates)
return render_template_string("<h1>{{ title }}</h1>", title="Hello")

Flask looks for templates in templates/ relative to the app root. For blueprints, each blueprint can have its own templates/ folder.

Template Folder Layout

templates/
  base.html
  index.html
  auth/
    login.html
  errors/
    404.html

Variable Output

{{ variable }}
{{ user.name }}
{{ items[0] }}
{{ user['email'] }}
{{ value | upper }}                   {# filter #}
{{ value | default("N/A") }}
{{ "<b>bold</b>" | safe }}            {# render raw HTML — only trusted content #}
{{ value | e }}                       {# explicit HTML escape (auto by default) #}

Filters

Built-in Filters

FilterUsageDescription
abs{{ -5 | abs }}Absolute value
capitalize{{ "hello" | capitalize }}First char upper
default{{ x | default("?") }}Fallback if undefined/falsy
default (boolean){{ x | default("?", true) }}Fallback if falsy (incl. 0, "")
dictsort{{ d | dictsort }}Sort dict by key
e / escape{{ v | e }}HTML-escape
filesizeformat{{ 1048576 | filesizeformat }}"1 MB"
first{{ list | first }}First element
float{{ "3.5" | float }}Convert to float
groupbysee belowGroup list of objects
int{{ "3" | int }}Convert to int
join{{ list | join(", ") }}Join iterable
last{{ list | last }}Last element
length{{ list | length }}Length of sequence
list{{ "abc" | list }}Convert to list
lower{{ "HI" | lower }}Lowercase
map{{ users | map(attribute="name") | list }}Map attr/filter
max{{ [1,3,2] | max }}Max value
min{{ [1,3,2] | min }}Min value
reject{{ list | reject("odd") | list }}Filter out matching
replace{{ "hi" | replace("i","ello") }}String replace
reverse{{ list | reverse | list }}Reverse iterable
round{{ 3.14 | round(1) }}Round number
safe{{ html | safe }}Mark as safe HTML
select{{ list | select("odd") | list }}Filter matching
slice{{ list | slice(3) }}Split into N chunks
sort{{ list | sort }}Sort iterable
string{{ 42 | string }}Convert to string
striptags{{ html | striptags }}Remove HTML tags
sum{{ list | sum }}Sum iterable
title{{ "hello world" | title }}Title Case
tojson{{ data | tojson }}JSON-safe JS literal
trim{{ " hi " | trim }}Strip whitespace
truncate{{ text | truncate(50) }}Truncate with "..."
unique{{ list | unique | list }}Deduplicate
upper{{ "hi" | upper }}Uppercase
urlencode{{ "/a b" | urlencode }}URL-encode string
urlize{{ text | urlize }}Convert URLs to links
wordcount{{ text | wordcount }}Count words
wordwrap{{ text | wordwrap(79) }}Wrap at N chars

Custom Filters

@app.template_filter("timeago")
def timeago_filter(dt):
    delta = datetime.now(timezone.utc) - dt   # dt must be timezone-aware
    if delta.days > 0:
        return f"{delta.days}d ago"
    seconds = delta.seconds
    if seconds >= 3600:
        return f"{seconds // 3600}h ago"
    if seconds >= 60:
        return f"{seconds // 60}m ago"
    return "just now"
{{ post.created_at | timeago }}

Control Flow

{# if / elif / else #}
{% if user.is_admin %}
  <span>Admin</span>
{% elif user.is_premium %}
  <span>Premium</span>
{% else %}
  <span>Free</span>
{% endif %}

{# for loop #}
{% for item in items %}
  <li>{{ loop.index }}. {{ item.name }}</li>
{% else %}
  <li>No items.</li>
{% endfor %}

Loop Variables

VariableDescription
loop.indexCurrent iteration (1-based)
loop.index0Current iteration (0-based)
loop.revindexIterations remaining (1-based)
loop.revindex0Iterations remaining (0-based)
loop.firstTrue on first iteration
loop.lastTrue on last iteration
loop.lengthTotal number of items
loop.depthNesting depth (1 in outermost)
loop.depth0Nesting depth (0 in outermost)
loop.previtemPrevious value (undefined on first)
loop.nextitemNext value (undefined on last)
loop.changed(*val)True if value changed since last iter

Template Inheritance

{# base.html #}
<!DOCTYPE html>
<html>
<head>
  <title>{% block title %}Site{% endblock %}</title>
  {% block head %}{% endblock %}
</head>
<body>
  <nav>{% block nav %}{% endblock %}</nav>
  {% block content %}{% endblock %}
  {% block scripts %}{% endblock %}
</body>
</html>
{# page.html #}
{% extends "base.html" %}

{% block title %}My Page — {{ super() }}{% endblock %}

{% block content %}
  <h1>Hello</h1>
  {{ super() }}    {# render parent block content too #}
{% endblock %}

Includes and Macros

{# include a partial #}
{% include "partials/_flash.html" %}
{% include "partials/_nav.html" ignore missing %}   {# no error if file missing #}
{% include ["special.html", "default.html"] %}      {# first existing file #}

{# macro: reusable template function #}
{% macro input(name, value="", type="text") %}
  <input type="{{ type }}" name="{{ name }}" value="{{ value }}">
{% endmacro %}

{{ input("username") }}
{{ input("password", type="password") }}

Import macros from another file:

{% from "macros.html" import input, card %}
{% import "macros.html" as ui %}
{{ ui.card(title="Welcome") }}

Comments and Whitespace

{# This is a comment — not sent to the browser #}

{%- block content -%}   {# strip whitespace around the tag #}
  ...
{%- endblock -%}

Template Variables from Python

# Per-render variables
render_template("page.html", user=user, items=items)

# App-wide (available in every template)
@app.context_processor
def inject_globals():
    return {
        "site_name": "My App",
        "current_year": datetime.now(timezone.utc).year,
        "is_premium": current_user.is_premium if current_user else False,
    }

Global Functions in Templates

Flask injects these automatically:

NameDescription
url_for(endpoint, **values)Build a URL
get_flashed_messages(with_categories=False)Flash message list
configThe app.config dict
requestCurrent request object
sessionCurrent session dict
gRequest-global storage

Add your own:

app.jinja_env.globals["format_date"] = lambda dt: dt.strftime("%b %d, %Y")

Flash Messages

# In a view:
from flask import flash
flash("Saved!", "success")
flash("Invalid input.", "error")
{# In a template: #}
{% for category, message in get_flashed_messages(with_categories=True) %}
  <div class="alert alert-{{ category }}">{{ message }}</div>
{% endfor %}

Auto-Escaping

  • Auto-escaping is on by default for .html, .htm, .xml, .xhtml templates.
  • Use {{ value | safe }} or Markup("trusted html") in Python to bypass.
  • Never mark user-supplied content as safe.
from markupsafe import Markup, escape

safe_html = Markup("<strong>Bold</strong>")
user_input = escape("<script>alert('xss')</script>")

Jinja Environment Customization

# Add an extension
app.jinja_env.add_extension("jinja2.ext.do")       # {% do list.append(x) %}
app.jinja_env.add_extension("jinja2.ext.loopcontrols")  # {% break %}, {% continue %}

# Change delimiters (rarely needed)
app.jinja_env.variable_start_string = "[["
app.jinja_env.variable_end_string = "]]"

# Trim blocks and lstrip blocks (cleaner output)
app.jinja_env.trim_blocks = True
app.jinja_env.lstrip_blocks = True

tojson Filter (Safe JS Embedding)

<script>
  const user = {{ user | tojson }};         {# safe for JS — escapes </script> #}
  const config = {{ config_dict | tojson(indent=2) }};
</script>

Always use | tojson (not | safe) when embedding Python data in <script> tags.