What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use Flask’s render_template() to render a Jinja template file and return the resulting HTML from a view. Import it from flask, place the template in the application’s templates directory, then pass values as keyword arguments:

return render_template('hello.html', person=name)

Flask loads the named file, makes person available to Jinja, renders the file on the server, and returns a string that Flask can send as the response. The examples below follow the Flask 3.1.x documentation.

A minimal working example

Create this project structure:

application.py
templates/
    hello.html

In application.py:

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/hello/<name>')
def hello(name):
    return render_template('hello.html', person=name)

if __name__ == '__main__':
    app.run(debug=True)

Create templates/hello.html:

<!doctype html>
<title>Hello</title>
<h1>Hello {{ person }}!</h1>

Run the application and open /hello/Ada. Flask finds hello.html, substitutes the route value for {{ person }}, and returns the rendered HTML.

What render_template() does

The Flask API defines the function as flask.render_template(template_name_or_list, **context). Its first argument identifies the template; keyword arguments become variables in the Jinja context. The documented return type is str.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Part What to provide What happens
template_name_or_list A template filename, a Jinja Template object, or a list of names/objects Flask renders the first template that exists when a list is supplied
**context Named values such as user=user or items=items Jinja exposes those names while evaluating the template
Return value None required from your view A rendered string, which a view may return directly

A normal view therefore needs no explicit response construction:

@app.route('/profile')
def profile():
    user = {'name': 'Ada', 'role': 'Engineer'}
    return render_template('profile.html', user=user)

If you need to add headers or choose a status code, wrap the rendered string with make_response():

from flask import Flask, make_response, render_template

@app.route('/download-page')
def download_page():
    html = render_template('report.html', title='Monthly report')
    response = make_response(html, 200)
    response.headers['X-Report-Version'] = '1'
    return response

Where Flask searches for templates

For a single-file application, Flask conventionally looks for a folder named templates next to the module containing the application. The constructor’s default is template_folder='templates'.

application.py
 templates/
  hello.html

For a package-based application, put the folder inside the package:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
application/
    __init__.py
    templates/
        hello.html

You can organize files below that directory. Refer to a nested file with a slash:

templates/
    admin/
        dashboard.html
return render_template('admin/dashboard.html')

If your files live elsewhere, configure the application with an explicit template folder when creating the Flask instance. Keep the configured path and the name passed to render_template() consistent.

Passing values into a template

Scalar values

Pass strings, numbers, booleans, or other Python values as keyword arguments:

@app.route('/welcome')
def welcome():
    return render_template('welcome.html', title='Welcome', visits=12, signed_in=True)

Use the names in Jinja:

<title>{{ title }}</title>
<p>Visits: {{ visits }}</p>
{% if signed_in %}
  <p>Signed in</p>
{% endif %}

Dictionaries and objects

Pass a dictionary under one context name and access its keys or attributes in the template:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
@app.route('/profile')
def profile():
    user = {'name': 'Ada', 'email': '[email protected]'}
    return render_template('profile.html', user=user)
<h1>{{ user.name }}</h1>
<a href="mailto:{{ user.email }}">{{ user.email }}</a>

For a dictionary whose keys are not valid attribute-style names, use bracket notation such as {{ user['display-name'] }}.

Lists and loops

Collections are passed the same way and iterated with Jinja control statements:

@app.route('/tasks')
def tasks():
    tasks = [
        {'title': 'Write tests', 'done': True},
        {'title': 'Deploy app', 'done': False},
    ]
    return render_template('tasks.html', tasks=tasks)
<ul>
{% for task in tasks %}
  <li>
    {% if task.done %}<del>{{ task.title }}</del>{% else %}{{ task.title }}{% endif %}
  </li>
{% else %}
  <li>No tasks yet.</li>
{% endfor %}
</ul>

Choosing a fallback template

The first parameter may be a list. Flask renders the first entry that exists, which is useful when a theme or optional override is available:

return render_template(['custom/home.html', 'home.html'], headline='News')

The list can contain template names or template objects. If none of the candidates exists, template loading fails.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Flask’s built-in template context

In a request, Flask makes several helpers available to Jinja in addition to the values you pass. The standard context includes config, request, session, g, url_for(), and get_flashed_messages().

<nav>
  <a href="{{ url_for('hello', name='Ada') }}">Greeting</a>
  {% if session.get('user_id') %}
    <span>Signed in</span>
  {% endif %}
</nav>

request, session, and g depend on an active request context. They are not available if you render a template in code that is running outside a request unless you explicitly establish the appropriate Flask context.

HTML escaping and safe data

Flask uses Jinja for rendering. With render_template(), autoescaping is enabled by default for templates ending in .html, .htm, .xml, .xhtml, and .svg. A value such as a submitted comment is therefore emitted as text rather than interpreted as markup.

@app.route('/echo')
def echo():
    return render_template('echo.html', message='<script>alert(1)</script>')
<p>{{ message }}</p>

Do not disable autoescaping casually. Flask documents Markup and Jinja’s |safe filter for content you have deliberately verified as safe:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<div class="trusted-copy">{{ trusted_html|safe }}</div>

Never apply |safe to untrusted user input merely to make formatting appear. Review the complete value and its origin before opting out of escaping.

Putting Python data into JavaScript

Pass the value into the template and use Jinja’s tojson filter inside a script block. This produces valid, safely rendered JavaScript data:

@app.route('/chart')
def chart():
    points = [{'day': 'Mon', 'value': 4}, {'day': 'Tue', 'value': 7}]
    return render_template('chart.html', points=points)
<script>
  const points = {{ points|tojson }};
  console.log(points);
</script>

Diagnosing template errors

TemplateNotFound

Flask’s tutorial demonstrates TemplateNotFound when a requested file has not been created. Check these items in order:

  1. Confirm the file exists under the application’s configured template folder.
  2. Check capitalization and spelling; a request for hello.html does not match Hello.html on case-sensitive filesystems.
  3. Use a slash for nested directories, such as admin/dashboard.html.
  4. Verify that the Flask constructor is pointing at the folder you actually use.
  5. Restart the development server after moving files if your tooling has not detected the change.

The page shows an empty or unexpected value

  • Compare the context name with the Jinja name exactly: render_template('x.html', person=name) requires {{ person }}.
  • Check whether a conditional or loop is receiving an empty collection.
  • For dictionaries, use bracket notation when a key contains punctuation or conflicts with an attribute.
  • If the value comes from request, session, or g, make sure the rendering occurs during a request context.

Markup appears as text

That is usually autoescaping working as designed. Keep the default behavior for untrusted content. Only mark HTML as safe after validating that the complete value is trusted and intended to be interpreted as markup.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

JavaScript fails after embedding data

Do not concatenate Python representations into a script manually. Pass the value through the template and use {{ value|tojson }}, then inspect the browser console for errors in the resulting data.

Testing a view that uses render_template()

Use Flask’s test client to request the route and assert on the rendered response. This exercises template lookup and context wiring without requiring a real browser:

import pytest
from application import app

@pytest.fixture
def client():
    app.config.update(TESTING=True)
    with app.test_client() as client:
        yield client

def test_hello(client):
    response = client.get('/hello/Ada')
    assert response.status_code == 200
    assert b'Hello Ada!' in response.data

A test that receives TemplateNotFound is valuable: it catches an incorrect filename or package layout before deployment. Tests can also verify escaped output by requesting a route with characters such as < and checking that they are not returned as executable markup.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Rendering performance and deployment details

Templates execute on the server before the response reaches the browser. Keep expensive database queries and network calls out of the template; load the data in the view, then pass the prepared values as context. Reuse a stable template structure and keep presentation logic in Jinja rather than embedding large Python computations in expressions.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

When deploying behind a production WSGI server, the template directory must be included in the application package or deployment artifact. A route can return the rendered string directly, while make_response() is the appropriate point for custom headers, cookies, or status codes.

Or skip the browser setup

If your goal is to capture the HTML produced by a deployed Flask route, ScreenshotNeo is the quickest API option: it removes cookie banners, newsletter popups, and chat widgets before capture, bills only clean shots, and reports the result through X-Page-Verdict and X-Billed headers. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

Point the request at a publicly reachable Flask URL (the example below uses the documented Stripe URL; replace it with your deployed route):

See the full parameter reference in the ScreenshotNeo documentation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', data));

ScreenshotNeo supports PNG, JPEG, WebP, and PDF output, plus full-page capture, CSS-selector element capture, device and viewport controls, retina scale, custom CSS or JavaScript, waits for selectors or network idle, request blocking, cookies and headers, geolocation, timezone, transparent backgrounds, resizing, caching, signed links, asynchronous jobs, webhooks, bulk capture, and a usage API. Every feature is available on every plan. The Free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Frequently Asked Questions

How do I refer to a template inside a subdirectory?

Use the path relative to the configured templates folder, with forward slashes—for example, render_template('admin/dashboard.html').

Can one view choose between two template files?

Yes. Pass a list as the first argument; Flask renders the first name or template object in that list that exists.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.