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.htmlVariable 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
| Filter | Usage | Description |
|---|---|---|
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 |
groupby | see below | Group 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
| Variable | Description |
|---|---|
loop.index | Current iteration (1-based) |
loop.index0 | Current iteration (0-based) |
loop.revindex | Iterations remaining (1-based) |
loop.revindex0 | Iterations remaining (0-based) |
loop.first | True on first iteration |
loop.last | True on last iteration |
loop.length | Total number of items |
loop.depth | Nesting depth (1 in outermost) |
loop.depth0 | Nesting depth (0 in outermost) |
loop.previtem | Previous value (undefined on first) |
loop.nextitem | Next 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") }}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:
| Name | Description |
|---|---|
url_for(endpoint, **values) | Build a URL |
get_flashed_messages(with_categories=False) | Flash message list |
config | The app.config dict |
request | Current request object |
session | Current session dict |
g | Request-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,.xhtmltemplates. - Use
{{ value | safe }}orMarkup("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.
Comments and Whitespace
{# This is a comment — not sent to the browser #} {%- block content -%} {# strip whitespace around the tag #} ... {%- endblock -%}