Jinja render text in HTML preserving line breaks

All whitespace, including newlines, is turned into a single space in HTML.

Your options, from best to worst:

  1. Put white-space: pre-wrap; on the containing element. This tells HTML to show all whitespace exactly as it appears in the source, including newlines. (You could also use a <pre> tag, but that will also disable word-wrapping, which you probably don't want.)
  2. Treat the plain text as Markdown and throw a Markdown processor at it—one of the things Markdown does is wrap paragraphs in <p>.
  3. In Python-land, do .replace('\n', '<br>'). But this leaves you vulnerable to XSS because there might be other HTML-like junk in the string, and fixing that is a bit of a pain.

As suggested by Martijn Pieters (by linking Flask snippet 28), there is also the possibility to add a custom filter for that. The link is outdated, because Flask Snippets are no longer provided.

So I will provide the snippet from web archive here:

nl2br filter

Posted by Dan Jacob on 2010-06-17 @ 05:03 and filed in Template Tricks

This is a nl2br (newline to <BR>) filter, adapted from the Jinja2 example here:

http://jinja.pocoo.org/2/documentation/api#custom-filters

import re

from jinja2 import evalcontextfilter, Markup, escape

_paragraph_re = re.compile(r'(?:\r\n|\r|\n){2,}')

app = Flask(__name__)

@app.template_filter()
@evalcontextfilter
def nl2br(eval_ctx, value):
    result = u'\n\n'.join(u'<p>%s</p>' % p.replace('\n', '<br>\n') \
        for p in _paragraph_re.split(escape(value)))
    if eval_ctx.autoescape:
        result = Markup(result)
    return result

The link above about custom-filters seems to be outdated, too. Here is a similar link from the current stable version 1.1: https://flask.palletsprojects.com/en/1.1.x/templating/#registering-filters

I'm not really sure why he uses such a complicated result computation. For me the following code worked and it's much simpler. Maybe, the variant above is better if you don't use autoescape (which I do not want to disable)?! Anyway, now both variants are available:

# custom_template_filters.py

from flask import Blueprint
from jinja2 import evalcontextfilter, Markup, escape

blueprint = Blueprint('custom_template_filters', __name__)

@evalcontextfilter
@blueprint.app_template_filter()
def newline_to_br(context, value: str) -> str:
    result = "<br />".join(re.split(r'(?:\r\n|\r|\n){2,}', escape(value)))

    if context.autoescape:
        result = Markup(result)

    return result

It is worth mentioning that the code snippet from Dan Jacob uses Python2, and I get the template filters running via Blueprint. For the sake of completeness I also provide the app.py using a factory method:

# app.py

from flask import Flask

def create_app() -> Flask:
    app = Flask(__name__)
    ...
    register_template_filters(flask_app=app)
    return app

def register_template_filters(flask_app: Flask) -> None:
    from . import custom_template_filters
    flask_app.register_blueprint(custom_template_filters.blueprint)
    return None

It is more or less an implementation detail how you will get the context filter working. The main idea is inside the function nlbr or newline_to_br itself. If you get the custom filter working, it is available in all your Jinja templates and you can use it like this:

{{ anystring | newline_to_br }}