The basic syntax for a Jinja hyperlink

A hyperlink in Jinja uses the same HTML <a> tag you would write by hand, but you can insert variables into the href attribute using Jinja's curly-brace syntax. The simplest form is:

<a href="{{ url }}">Link text</a>

When Jinja renders this template, it replaces {{ url }} with whatever value the url variable holds. If url equals https://example.com, the output becomes <a href="https://example.com">Link text</a>. The link text — the part a user actually clicks — stays as you wrote it, or can also be a variable.

You pass the url variable to your template from your Python code (or whatever backend language you are using). The template does not know where the value comes from; it only knows to insert it where you marked the placeholder.

Key Takeaways

  • Wrap any variable you want to insert into a hyperlink with double curly braces: {{ variable_name }}.
  • The variable must be passed to the template from your backend code before the template renders.
  • Use Jinja filters like {{ url | urlencode }} when the URL contains spaces or special characters that need escaping.
  • The url_for() function generates URLs dynamically based on your process's routes, so you do not have to hardcode them.

Passing variables from your backend to the template

In a Flask process (the most common framework that uses Jinja), you pass variables to a template using the render_template() function. Here is a real example:

In your Python file, you might write:

return render_template('page.html', url='https://example.com', link_text='Click here')

Then in your page.html template, you use those variables:

<a href="{{ url }}">{{ link_text }}</a>

The names you use in render_template() must match the names you use in the curly braces. If you pass url='...' but write {{ link }} in the template, Jinja will render an empty string because no variable named link exists.

Using url_for() to generate links automatically

Hardcoding URLs into templates is fragile — if you change a route in your process, every template that links to it breaks. Jinja's url_for() function generates URLs based on your process's route definitions, so they stay in sync.

In Flask, you define a route like this:

@app.route('/products/<int:product_id>') def show_product(product_id): return render_template('product.html', product_id=product_id)

In your template, instead of writing the URL by hand, use url_for():

<a href="{{ url_for('show_product', product_id=123) }}">View Product</a>

Jinja evaluates url_for() and inserts the correct URL. If you later change the route to /items/<int:item_id>, you only update the route definition — the template still works because it calls url_for() by the function name, not the URL path.

Handling URLs with spaces and special characters

If a URL contains spaces, ampersands, or other characters that are not safe in HTML attributes, you must encode them. Jinja provides the urlencode filter for this:

<a href="{{ search_url | urlencode }}">Search results</a>

The pipe symbol (|) tells Jinja to pass the variable through the urlencode filter before inserting it. If search_url contains query=hello world, the filter converts the space to %20, producing valid HTML.

You can also use the safe filter if you are certain the URL is already safe and you want Jinja to treat it as trusted content. This is less common and should only be used when you control the source of the URL.

Building links from data in a loop

Often you have a list of items and need to create a link for each one. Jinja's for loop lets you do this:

<ul> {% for product in products %} <li><a href="{{ url_for('show_product', product_id=product.id) }}">{{ product.name }}</a></li> {% endfor %} </ul>

Your backend passes a list called products, where each item has an id and a name. Jinja loops through the list, and for each product, it creates a link using that product's ID and name. The {% for %} and {% endfor %} tags tell Jinja where the loop starts and stops.

This pattern is how product listings, search results, and navigation menus are built. The loop runs once for each item, so if you have 50 products, Jinja generates 50 links.

Conditional links based on user state

You may want to show a link only if a condition is true — for example, only show a "Log out" link if the user is logged in. Use Jinja's if statement:

{% if user %} <a href="{{ url_for('logout') }}">Log out</a> {% else %} <a href="{{ url_for('login') }}">Log in</a> {% endif %}

If the user variable exists and is not empty, Jinja renders the logout link. Otherwise, it renders the login link. The {% endif %} tag closes the conditional block.

You can also combine conditions: {% if user and user.is_admin %} renders the block only if a user exists and their is_admin property is true. This is how you hide admin links from regular users or show different navigation based on login state.

Common mistakes and how to fix them

Forgetting the curly braces: Writing <a href="url"> instead of <a href="{{ url }}"> will render the literal text "url" as the href, not the value of the variable. Always use double curly braces for variables.

Mismatched variable names: If you pass product_url from your backend but write {{ product_link }} in the template, Jinja renders nothing. Check that the name in the template matches exactly what you passed from your code.

Forgetting to pass the variable: If you use {{ url }} in a template but never pass url= in render_template(), the variable is undefined. Your template will render an empty href, creating a broken link. Always verify that every variable you use in a template is passed from your backend.

Not encoding special characters: If a URL contains a space or an ampersand and you do not encode it, the HTML may be malformed. Use the urlencode filter or may support your backend passes already-encoded URLs.

Frequently Asked Questions

What is the difference between {{ }} and {% %} in Jinja?

Double curly braces {{ }} insert a value into the output — they are for variables and expressions. Curly braces with a percent sign {% %} are for logic like loops and conditionals — they do not produce output themselves, only control what gets rendered.

Can I use a function call inside a hyperlink?

Yes. <a href="{{ url_for('function_name', param=value) }}"> calls url_for() and inserts the result. You can also call custom filters or other functions your backend has made available to the template.

How do I make a link open in a new tab?

Add the target="_blank" attribute: <a href="{{ url }}" target="_blank">Link</a>. This is plain HTML, not Jinja-specific. You can also add rel="noopener noreferrer" for security when linking to external sites.

What happens if the variable is empty or None?

If url is empty or None, Jinja renders <a href=""> or <a href="None">, creating a broken link. Use an if statement to check: {% if url %}<a href="{{ url }}">Link</a>{% endif %} only renders the link if the variable has a value.

Can I pass a list of URLs and create multiple links?

Yes, use a for loop. Pass a list from your backend, then loop through it in the template: {% for link in links %}<a href="{{ link.url }}">{{ link.text }}</a>{% endfor %}. Each iteration creates one link.