Jinja gera HTML e outros formatos textuais a partir de templates. A configuração central deve controlar carregamento, valores indefinidos e escape, enquanto regras de negócio permanecem em Python.

from jinja2 import Environment, FileSystemLoader, StrictUndefined, select_autoescape

env = Environment(
    loader=FileSystemLoader("templates"),
    autoescape=select_autoescape(["html", "xml"]),
    undefined=StrictUndefined,
)
template = env.get_template("produto.html")
html = template.render(produto={"nome": "Curso Python"})

StrictUndefined revela variáveis ausentes cedo. autoescape reduz risco de XSS em HTML, mas não torna seguro inserir dados em qualquer contexto, como JavaScript ou URL. Não marque conteúdo do usuário como seguro para contornar o escape.

Use herança para layout, include para fragmentos e macros para apresentação repetida. Evite consultas, autorização e transformações complexas no template. O guia de Flask em Python mostra um contexto web relacionado.

A documentação oficial do Jinja, consultada em 22 de julho de 2026, cobre Environment, loaders, escape e herança. Crie um único ambiente por configuração e teste a saída com casos que contenham caracteres especiais.

Instalação e organização do projeto

Instale a biblioteca em um ambiente virtual com python -m pip install Jinja2. O pacote instalado se chama Jinja2, mas o módulo importado é jinja2. Em uma aplicação pequena, uma estrutura com templates/, static/ e o código Python já separa responsabilidades. O loader resolve nomes dentro da raiz configurada, por isso prefira env.get_template("emails/boas-vindas.html") a montar caminhos recebidos do usuário.

Um Environment concentra as políticas da aplicação. Criá-lo uma vez também permite reaproveitar o cache interno de templates. Configurações diferentes, como HTML com escape e arquivos de configuração sem escape, merecem ambientes separados. Não desative auto_reload ou altere o cache sem medir: os padrões costumam atender desenvolvimento e produção.

from pathlib import Path
from jinja2 import Environment, FileSystemLoader, StrictUndefined, select_autoescape

BASE_DIR = Path(__file__).resolve().parent

env = Environment(
    loader=FileSystemLoader(BASE_DIR / "templates"),
    autoescape=select_autoescape(
        enabled_extensions=("html", "htm", "xml"),
        default_for_string=True,
    ),
    undefined=StrictUndefined,
    trim_blocks=True,
    lstrip_blocks=True,
)

trim_blocks e lstrip_blocks ajudam a controlar linhas vazias geradas por blocos, mas não alteram o texto dentro das variáveis. Já default_for_string=True protege templates criados com env.from_string(). Ainda assim, from_string() deve receber texto controlado pela aplicação, nunca uma expressão fornecida por um visitante.

Variáveis, filtros e testes

Expressões entre {{ ... }} imprimem valores. Blocos {% ... %} controlam fluxo, e comentários {# ... #} não aparecem no resultado. Acesse somente dados preparados pelo código Python. Um dicionário simples torna explícito o contrato entre a camada de negócio e a apresentação.

<h2>{{ pagina.titulo }}</h2>

{% if produtos %}
  <ul>
  {% for produto in produtos %}
    <li>
      {{ produto.nome }}
      <span>{{ produto.preco_centavos | moeda_brl }}</span>
    </li>
  {% endfor %}
  </ul>
{% else %}
  <p>Nenhum produto disponível.</p>
{% endif %}

Filtros transformam um valor para apresentação. Registre funções pequenas, determinísticas e fáceis de testar. Uma função de moeda pode receber centavos e devolver texto formatado; ela não deve consultar câmbio nem banco de dados. Testes, usados com is, respondem perguntas como valor is defined ou numero is odd.

def moeda_brl(centavos: int) -> str:
    reais = centavos / 100
    return f"R$ {reais:,.2f}".replace(",", "_").replace(".", ",").replace("_", ".")

env.filters["moeda_brl"] = moeda_brl

O filtro default pode oferecer um valor substituto, mas não deve esconder campos obrigatórios. Com StrictUndefined, um nome digitado errado gera erro em vez de produzir uma página incompleta silenciosamente. Capture esse erro na borda da aplicação, registre o nome do template e apresente uma resposta genérica ao usuário, sem expor caminhos internos.

Herança de layout e blocos

Herança evita copiar cabeçalho, navegação e rodapé. O template filho deve usar extends como primeira decisão estrutural e preencher blocos definidos pela base.

{# templates/base.html #}
<!doctype html>
<html lang="pt-BR">
  <head>
    <meta charset="utf-8">
    <title>{% block title %}Minha aplicação{% endblock %}</title>
  </head>
  <body>
    <main>{% block content required %}{% endblock %}</main>
  </body>
</html>
{# templates/produtos/detalhe.html #}
{% extends "base.html" %}

{% block title %}{{ produto.nome }} | Minha aplicação{% endblock %}

{% block content %}
  <h2>{{ produto.nome }}</h2>
  <p>{{ produto.descricao }}</p>
{% endblock %}

O modificador required obriga descendentes a implementar um bloco e ajuda a detectar páginas incompletas. super() inclui o conteúdo do bloco pai quando a extensão deve acrescentar, não substituir. Use include para um fragmento que compartilha o contexto atual e import para macros. Um componente que depende de muitas variáveis implícitas fica difícil de reutilizar; prefira passar argumentos claros.

Macros reutilizáveis

Macros funcionam como funções de apresentação. Elas são úteis para botões, campos e cartões cujo HTML precisa permanecer consistente. Não transforme cada linha em macro: componentes pequenos demais escondem a estrutura e dificultam depuração.

{# templates/componentes.html #}
{% macro link_acao(texto, url, variante="primaria") -%}
  <a class="botao botao--{{ variante | e }}" href="{{ url }}">{{ texto }}</a>
{%- endmacro %}
{% from "componentes.html" import link_acao %}
{{ link_acao("Ver detalhes", url_produto) }}

O escape aplicado a texto depende do ambiente. O |e explícito na classe reforça a intenção, mas uma variante deve vir de uma lista permitida no Python. Escape não é validação: uma URL javascript: pode continuar semanticamente perigosa mesmo com caracteres HTML escapados. Gere URLs com o roteador da aplicação ou valide esquema e destino antes de enviá-las ao template.

Autoescape, Markup e contextos

Em HTML, Jinja converte caracteres como <, > e & para entidades quando o autoescape está ativo. Isso protege conteúdo inserido em nós de texto e atributos devidamente delimitados. Sempre coloque atributos entre aspas. Para dados em JavaScript, use serialização JSON adequada, como o filtro tojson, em vez de interpolar strings.

<script>
  const configuracao = {{ configuracao_publica | tojson }};
</script>

O filtro safe e objetos Markup declaram que um trecho já é confiável. Essa declaração transfere a responsabilidade de segurança para quem criou o valor. Reserve-a para HTML estático produzido e revisado pela aplicação. Se usuários podem escrever Markdown, use um sanitizador HTML com política explícita depois da conversão; Jinja não é sanitizador.

Templates fornecidos por terceiros podem acessar atributos e executar operações permitidas pela linguagem de templates. SandboxedEnvironment restringe parte desse comportamento, mas não substitui isolamento de processo, limite de tempo, memória e tamanho de saída. Para templates realmente não confiáveis, a escolha mais segura costuma ser oferecer um conjunto pequeno de campos configuráveis em vez de aceitar código Jinja.

Renderização, streaming e arquivos de texto

render() devolve uma string completa. Para saídas grandes, generate() produz partes progressivamente e stream() permite controlar buffering. Streaming reduz o pico de memória, mas erros tardios podem acontecer depois que parte da resposta já foi enviada. Valide os dados obrigatórios antes de começar e avalie se a complexidade compensa.

Jinja também gera e-mails, arquivos SQL e configurações, porém o escape deve corresponder ao formato. Escape HTML não protege SQL, shell, CSV ou YAML. Nunca use template para montar consultas SQL com dados externos; use parâmetros do driver. Para texto simples, crie um ambiente separado com autoescape=False e trate as regras específicas do destino.

def renderizar_email(nome: str, link: str) -> str:
    template = env.get_template("emails/confirmacao.html")
    return template.render(nome=nome, link=link)

Testes e diagnóstico

Teste templates com dados mínimos, dados completos e caracteres especiais. Uma asserção sobre elementos relevantes é mais estável que comparar todo o HTML. Também vale carregar antecipadamente os templates principais no teste para detectar erro de sintaxe, arquivo ausente e bloco obrigatório não implementado.

from markupsafe import escape


def test_nome_e_escapado():
    template = env.from_string("<p>{{ nome }}</p>")
    saida = template.render(nome="<script>alert(1)</script>")
    assert str(escape("<script>alert(1)</script>")) in saida
    assert "<script>" not in saida

Em produção, registre a exceção com contexto técnico no log, mas não inclua segredos nem o dicionário inteiro de renderização. Um template pode receber tokens, endereços ou dados pessoais. A página de erro pública deve permanecer genérica.

Checklist de uma integração confiável

Antes de publicar, confirme que templates HTML usam autoescape, variáveis obrigatórias falham cedo e caminhos vêm de nomes controlados. Verifique ainda atributos entre aspas, URLs construídas por mecanismo confiável, ausência de safe sobre entrada externa e nenhum acesso a banco ou rede dentro de filtros.

Mantenha uma convenção de diretórios, nomes e componentes. Revise a saída com HTML válido, navegação por teclado e idiomas corretos. Jinja resolve a composição textual; acessibilidade, semântica e segurança contextual continuam sendo responsabilidades da aplicação. Essa divisão clara produz templates menores, previsíveis e mais fáceis de evoluir.