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

Use render_template() inside a Flask view, put the requested file in a templates directory, and pass page data as keyword arguments. Flask asks Jinja to render that file and returns the resulting HTML as a string that the view can return directly.

Render your first Jinja template

Import render_template from Flask, create a route, and return the function call. This complete example follows the Flask 3.1.x API:

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 this file relative to application.py:

templates/hello.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Hello</title>
  </head>
  <body>
    <h1>Hello {{ person }}!</h1>
  </body>
</html>

Start the application and visit http://127.0.0.1:5000/hello/Ada. Jinja replaces {{ person }} with the route value, so the page displays “Hello Ada!”. Flask’s quickstart documents this server-side rendering flow in its quickstart.

What render_template accepts and returns

The documented signature is flask.render_template(template_name_or_list, **context). The first argument identifies what to render; keyword arguments become variables in the Jinja context. The documented return type is str, and a view may return that string directly. See the Flask API reference for the complete signature.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Argument What to pass Result
template_name_or_list A template name, a Jinja Template object, or a list of names/objects Flask renders the named template, or the first entry in a list that exists
**context Keyword arguments such as person=name or user=user Those names are available to expressions in the template
Return value No special response wrapper required for ordinary pages A rendered Unicode string

For example, a dictionary is passed as one named value, not expanded accidentally:

from flask import render_template

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

Place templates in Flask’s search folder

By default, Flask looks for a folder named templates next to a single-file application module or inside an application package. The application constructor’s default is template_folder='templates', using a filesystem loader when that folder is configured. These layouts match the official quickstart.

Single-file application

application.py
 templates/
  hello.html
  profile.html

Run the application from the directory containing application.py, and call render_template('hello.html'). The name is relative to the template folder; do not include the folder name itself.

Package application

application/
 __init__.py
 templates/
  hello.html

When you create the Flask object inside application/__init__.py, Flask searches the package’s templates directory. If you intentionally use another directory, configure it explicitly when constructing Flask, for example Flask(__name__, template_folder='web_templates').

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

Pass values, lists and conditional data

Every keyword argument after the template name becomes a template variable. Jinja expressions can read attributes and dictionary keys, iterate over lists, and choose content conditionally:

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

Flask also supplies standard context helpers while a request is active. The documented names include config, request, session, g, url_for(), and get_flashed_messages(). Request-bound values such as request, session, and g are not available when you render outside an active request context. Flask explains this context in its templating guide.

Understand autoescaping before displaying user input

Flask uses Jinja as its template engine. For files ending in .html, .htm, .xml, .xhtml, or .svg, Flask enables autoescaping when the file is rendered with render_template(). Characters such as < and > in an ordinary string are escaped instead of interpreted as markup.

@app.route('/search')
def search():
    return render_template('search.html', query='<script>alert(1)</script>')
<p>You searched for: {{ query }}</p>

Do not disable autoescaping casually and do not mark untrusted input as safe. Flask documents Markup and Jinja’s |safe filter as explicit ways to mark content safe; use them only after you have controlled and validated the content. The escaping rules and exceptions are described in the templating documentation.

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

Insert server data into JavaScript safely

Templates execute on the server before the response reaches the browser. To put a Python value into a script, pass it through the template context and use Jinja’s tojson filter. The Flask quickstart recommends this approach for valid, safely rendered JavaScript data:

@app.route('/dashboard')
def dashboard():
    chart = {'labels': ['Mon', 'Tue'], 'values': [3, 5]}
    return render_template('dashboard.html', chart=chart)
<script>
  const chart = {{ chart|tojson }};
  console.log(chart.values);
</script>

Do not build JavaScript by concatenating an unescaped Python string into a <script> block. tojson produces a JavaScript-compatible representation while applying the escaping required by the template context.

Set a status code or headers with make_response

For a normal page, returning the rendered string is enough. If you need to set headers or a status code, wrap the result with make_response:

from flask import make_response, render_template

@app.route('/created')
def created():
    html = render_template('created.html')
    response = make_response(html, 201)
    response.headers['X-Page-Type'] = 'created'
    return response

The API documentation shows this pattern for turning rendered output into a response object while retaining control over HTTP metadata.

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

Use fallback templates when more than one name is acceptable

The first argument can be a list of template names or template objects. Flask renders the first entry that exists:

@app.route('/notice')
def notice():
    return render_template(['notice.html', 'default-notice.html'], message='Maintenance')

This is useful when a deployment can provide a customized file but should fall back to a default. Keep the fallback order deliberate: Flask stops at the first existing entry.

Fix the errors developers see most often

Symptom Likely cause Fix
jinja2.exceptions.TemplateNotFound The file is missing, in the wrong directory, or the name differs from the argument Confirm the file exists under the application’s configured templates folder and that spelling and capitalization exactly match render_template(). Flask demonstrates this failure in its templates tutorial.
UndefinedError for a variable The view did not pass the keyword, or the template uses a different name Compare the context names, such as user=user with {{ user.name }}, and check conditional branches for missing values.
BuildError from url_for() The endpoint name or required route argument is wrong Use the view function’s endpoint name and supply every dynamic route value required by that route.
HTML appears as text The value was escaped, or the file extension is not one of Flask’s autoescaped extensions Keep untrusted content escaped. If trusted markup must be rendered, review the value and an explicit safe-marking operation carefully rather than disabling escaping globally.
request, session, or g is unavailable Rendering is happening outside an active request context Move the call into a request-handling view or establish the appropriate Flask context for a controlled background operation; do not assume request-bound objects exist everywhere.
Changes to a template do not appear A different file is being loaded, or a cached/browser response is being viewed Verify the resolved filename and URL, restart the development server if necessary, and hard-refresh the browser. Avoid treating development debugging behavior as a production cache strategy.

Organize a maintainable render flow

  • Keep view functions focused: load or calculate data in Python, then pass named values to the template.
  • Use descriptive context names: render_template('order.html', order=order, errors=errors) is easier to maintain than positional conventions.
  • Keep presentation in Jinja: use loops and conditionals for display decisions, but keep database access and business rules in Python.
  • Choose extensions deliberately: HTML-like extensions receive Flask’s documented autoescaping defaults.
  • Test the response: Flask’s test client can request the route and let you assert the status code and expected text without opening a browser.

For a production application, also review the data you pass into templates. Autoescaping protects the HTML output path, but it does not make every use of a value safe in JavaScript, URLs, CSS, or an HTML attribute with special requirements. Use context-appropriate escaping and tojson for script data.

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

Or skip the browser setup

If your goal is to capture the rendered Flask page as an image or PDF for a test artifact, preview, or documentation, ScreenshotNeo can request a public URL directly. Before capture it accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

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

Replace the example URL with a publicly reachable route from your Flask deployment. The parameter names used by other screenshot APIs also work, which can simplify a migration. Full API documentation is at https://screenshotneo.com/docs/.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" 
  -d access_key=YOUR_API_KEY 
  --data-urlencode url=https://your-domain.example/hello/Ada 
  -o shot.webp

Python

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={
        "access_key": "YOUR_API_KEY",
        "url": "https://your-domain.example/hello/Ada",
    },
    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://your-domain.example/hello/Ada'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const bytes = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', bytes);

ScreenshotNeo includes full-page capture with lazy images loaded, CSS-selector element capture, device presets or custom viewports, dark mode, retina scale, PDF settings, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, configurable caching, signed links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Plans include 1,000 shots per month free with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

FAQ

Does render_template render in the browser?

No. Jinja runs on the Flask server, and the browser receives the resulting string as the HTTP response.

What should I check first when a page is blank?

Confirm that the route reaches the intended view, the template name points to the configured templates folder, and the template actually emits HTML outside any false conditional branch.

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.

Frequently Asked Questions

Can I return the rendered string without creating a Response object?

Yes. Flask converts a view’s returned string into a response automatically. Use make_response only when you need to set headers or a specific status code.

Why is a request value unavailable in a background task?

request, session, and g are request-bound context objects. A background task does not automatically have the active request context, so pass the data it needs explicitly or arrange an appropriate Flask context.

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.