Skip to main content

jinja2-patterns

Jinja2 template patterns for Flask - base layouts with template inheritance and blocks, reusable macros for form fields, custom filters, url_for static links, loops and conditionals, flash messages and CSRF tokens in forms. Use it for any task that writes or explains the template side of a Flask app, in any language. For routes and blueprint structure use app-patterns; for a template that errors or is not found use troubleshoot.

Source facts

Repository
Agents-Store/codex-plugins
Last source activity
November 1, 2026 at 02:46
Detected SKILL.md language
English
Stars
0
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
2 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
jinja2-patterns
description
Jinja2 template patterns for Flask - base layouts with template inheritance and blocks, reusable macros for form fields, custom filters, url_for static links, loops and conditionals, flash messages and CSRF tokens in forms. Use it for any task that writes or explains the template side of a Flask app, in any language. For routes and blueprint structure use app-patterns; for a template that errors or is not found use troubleshoot.
# Jinja2 Template Patterns Production patterns for Jinja2 templating in Flask applications. ## Template Inheritance ### Base Template ```jinja2 {# templates/base.html #} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{% block title %}App{% endblock %}</title> <link rel="stylesheet" href="{{ url_for('static', filename='css/base.css') }}"> {% block extra_css %}{% endblock %} </head> <body> {% if current_user.is_authenticated %} <nav> <a href="{{ url_for('dashboard.dashboard') }}">Dashboard</a> <form method="post" action="{{ url_for('auth.logout') }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> <button type="submit">Logout</button> </form> </nav> {% endif %} <main> {# Flash messages #} {% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} {% for category, message in messages %} <div class="alert alert-{{ category }}">{{ message }}</div> {% endfor %} {% endif %} {% endwith %} {% block content %}{% endblock %} </main> <script src="{{ url_for('static', filename='js/app.js') }}"></script> {% block extra_js %}{% endblock %} </body> </html> ``` ### Child Template ```jinja2 {# templates/clients.html #} {% extends "base.html" %} {% block title %}Clients - myapp{% endblock %} {% block extra_css %} <link rel="stylesheet" href="{{ url_for('static', filename='css/clients.css') }}"> {% endblock %} {% block content %} <h1>Clients</h1> <div class="client-list"> {% for client in clients %} <div class="client-card"> <h3>{{ client.name }}</h3> <p>{{ client.phone }}</p> </div> {% else %} <p>No clients yet.</p> {% endfor %} </div> {% endblock %} ``` ## Control Structures ### Conditionals ```jinja2 {% if user.status == 'vip' %} <span class="badge vip">VIP</span> {% elif user.status == 'regular' %} <span class="badge regular">Regular</span> {% else %} <span class="badge new">New</span> {% endif %} ``` ### Loops ```jinja2 {% for item in items %} <div class="{{ 'even' if loop.index is even else 'odd' }}"> {{ loop.index }}. {{ item.name }} </div> {% endfor %} ``` Loop variables: `loop.index` (1-based), `loop.index0` (0-based), `loop.first`, `loop.last`, `loop.length`. ## Filters ### Built-in Filters ```jinja2 {{ name|title }} {# "john doe" → "John Doe" #} {{ price|int }} {# 19.99 → 19 #} {{ items|length }} {# List length #} {{ text|truncate(50) }} {# Truncate with ellipsis #} {{ date|default('N/A', true) }} {# N/A if undefined, None or falsy (without true, only if undefined) #} {{ html_content|safe }} {# Mark as safe HTML (no escaping) #} {{ list|join(', ') }} {# Join list items #} {{ date_obj|string }} {# Convert to string #} ``` ### Custom Filters ```python # In app.py or a filters module @app.template_filter('currency') def currency_filter(value): return f"{value:,.2f} USD" @app.template_filter('timeformat') def timeformat_filter(time_obj): return time_obj.strftime('%H:%M') ``` ```jinja2 {{ appointment.price|currency }} {# "1,500.00 USD" #} {{ appointment.time|timeformat }} {# "14:30" #} ``` ## Forms ### Basic Form `csrf_token()` exists only when `CSRFProtect` is initialised (`csrf.init_app(app)`, see the `app-patterns` skill). Without it the template raises `UndefinedError: 'csrf_token' is undefined`, and with it every `POST` without a token gets a 400. ```jinja2 <form method="POST" action="{{ url_for('clients.add_client') }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> <label for="name">Name</label> <input type="text" name="name" id="name" required value="{{ request.form.get('name', '') }}"> <label for="phone">Phone</label> <input type="tel" name="phone" id="phone" required> <button type="submit">Add Client</button> </form> ``` ### Select with Pre-selected Value ```jinja2 <select name="status"> {% for option in ['new', 'regular', 'vip'] %} <option value="{{ option }}" {{ 'selected' if client.status == option }}> {{ option|title }} </option> {% endfor %} </select> ``` ## Macros Reusable template components: ```jinja2 {# templates/macros/forms.html #} {% macro input(name, label, type='text', value='', required=false) %} <div class="form-group"> <label for="{{ name }}">{{ label }}</label> <input type="{{ type }}" name="{{ name }}" id="{{ name }}" value="{{ value }}" {{ 'required' if required }}> </div> {% endmacro %} ``` ```jinja2 {# Usage in templates #} {% from "macros/forms.html" import input %} {{ input('name', 'Client Name', required=true) }} {{ input('email', 'Email', type='email') }} {{ input('phone', 'Phone', type='tel', required=true) }} ``` ## Static Files ```jinja2 <link rel="stylesheet" href="{{ url_for('static', filename='css/base.css') }}"> <script src="{{ url_for('static', filename='js/app.js') }}"></script> <img src="{{ url_for('static', filename='images/logo.png') }}" alt="Logo"> ``` Always use `url_for('static', ...)` instead of hardcoded paths: it handles URL prefixes. It does **not** add cache busting (`/static/css/base.css` stays the same URL after the file changes). Add a version query string, or use hashed file names from your front-end build: ```python # app.py: imports at the top of the file import os from flask import current_app, url_for # inside create_app() @app.template_global() def static_v(filename): """url_for('static') plus ?v=<mtime>, so browsers refetch changed files.""" path = os.path.join(current_app.static_folder, filename) version = int(os.stat(path).st_mtime) if os.path.exists(path) else 0 return url_for('static', filename=filename, v=version) ``` ```jinja2 <link rel="stylesheet" href="{{ static_v('css/base.css') }}"> {# /static/css/base.css?v=1760000000 #} ``` ## Context Variables Variables available in all templates without explicit passing: | Variable | Source | Description | |----------|--------|-------------| | `current_user` | Flask-Login | Authenticated user object | | `request` | Flask | Current request object | | `session` | Flask | Session data | | `config` | Flask | App configuration | | `g` | Flask | Per-request globals | | `csrf_token()`, `csrf_meta_tag()` | Flask-WTF | Defined only after `CSRFProtect` is initialised (`csrf_meta_tag` needs 1.3+) | | Custom | `@app.context_processor` | User-defined globals |
View on GitHub