Displaying data
Now that you have a working PyBlade project, let's make your templates dynamic.
One of the most common things you'll do in a template is display data provided by your application.
So far, our template contains only static HTML. Let's make it a little more useful by passing data from our Python application into the template.
The process is simple:
- Your application creates or retrieves some data.
- The data is passed to the template as part of the template context.
- The template uses PyBlade's
{{ }}syntax to display that data.
Passing data to a template
The exact way you pass data to a template depends on the web framework you're using. Let's look at a simple example with Django and Flask.
from django.shortcuts import render
def show_greeting(request):
context = {
"name": "John Doe",
}
return render(request, "greeting", context)In Django, data is passed to a template through the context dictionary. Here, we are passing a name variable containing "John Doe".
from pyblade.flask import render
@app.route("/greeting")
def show_greeting():
context = {
"name": "John Doe",
}
return render("greeting", context)Flask works in a similar way. We pass the data as part of the context when rendering the template.
Once the data has been passed to the template, we can access it using PyBlade's {{ }} syntax.
Displaying a variable
To display a value, place its name between double curly braces:
<p>Hello, {{ name }}!</p>When PyBlade renders this template, {{ name }} is replaced with the value of the name variable.
Since our application passed "John Doe" as the value of name, the resulting HTML will be:
<p>Hello, John Doe!</p>That's all you need to display a variable in a PyBlade template.
The {{ }} syntax can be used to display strings, numbers, object properties, and other values available in the template context. We'll explore these possibilities throughout this section.
Automatic HTML escaping
Values displayed with {{ }} are automatically HTML-escaped by PyBlade.
For example, if name contains:
<script>alert("Hello")</script>PyBlade will escape the HTML instead of interpreting it as markup. This helps protect your application against common Cross-Site Scripting (XSS) vulnerabilities when displaying data coming from untrusted sources such as user inputs.
This escaping happens automatically, so {{ }} should be your default syntax for displaying data.
Sometimes, you may intentionally want to render HTML without escaping it. PyBlade provides a separate syntax for that use case, which we'll cover in Rendering Unescaped HTML.
Displaying data with default values
Sometimes, a variable may be available in the template context but empty or otherwise evaluate to a falsy value. In these cases, you can provide a fallback directly in your template using Python's or operator.
For example:
<p>Hello, {{ name or "Guest" }}!</p>If name contains "John Doe", PyBlade displays it:
<p>Hello, John Doe!</p>If name is defined but contains a falsy value, such as None or an empty string, PyBlade uses "Guest" instead:
<p>Hello, Guest!</p>Keep in mind that or does not silently handle undefined variables. If name has not been defined in the template context, PyBlade raises a NameError and displays a detailed error page showing where the error occurred and providing a quick fix to help you resolve it.
This behavior helps you catch missing data early instead of silently hiding mistakes in your templates.
Rendering unescaped HTML
By default, PyBlade automatically escapes all output inside {{ ... }}.
This means HTML tags are converted into safe text.
For example:
{{ message }}If:
message = "<strong>Welcome back!</strong>"The browser will render:
<strong>Welcome back!</strong>So the <strong> tag is displayed as text — not interpreted as HTML.
But, in some cases, you may intentionally want the browser to interpret the HTML tags. For that purpose, you can use the {!! !!} syntax as following:
{!! message !!}With
message = "<strong>Welcome back!</strong>"The browser will render
<strong>Welcome back!</strong>Now the <strong> tag is parsed and applied correctly.
Rendering unescaped content is dangerous especially if the data comes from untrusted sources.
Use {!! !!} with caution. Always ensure that the content is trusted and safe before rendering.
Accessing nested values
When working with data in your templates, you will often need to access values nested inside dictionaries, lists, or tuples.
PyBlade provides a convenient dot notation that makes navigating this data concise and readable. You can use it to access dictionary keys, sequence indexes, and combine both when working with nested structures.
Dictionary keys
When a dictionary key is a valid Python identifier, you can access it using dot notation.
For example, instead of writing:
{{ customer["name"] }}you can write:
{{ customer.name }}Both forms access the same value.
Suppose your application provides the following context:
context = {
"customer": {
"name": "Alice",
"email": "alice@example.com",
}
}You can access the customer's name using:
<p>{{ customer.name }}</p>Which produces:
<p>Alice</p>Dot notation also works with nested dictionaries:
context = {
"customer": {
"address": {
"city": "Goma",
}
}
}You can access the city with:
<p>{{ customer.address.city }}</p>Which produces:
<p>Goma</p>List and tuple indexes
The same notation can be used to access elements of lists and tuples.
In regular Python syntax, you would access an element using brackets:
{{ countries[0] }}PyBlade also lets you use the index directly after a dot:
{{ countries.0 }}For example:
context = {
"countries": ["France", "Germany", "Japan"],
}You can access the first country with:
<p>{{ countries.0 }}</p>Which produces:
<p>France</p>Both countries[0] and countries.0 access the same element. The dot notation simply provides a shorter and more consistent way to navigate data in your templates.
Combining keys and indexes
You can freely combine dictionary keys and sequence indexes to navigate more complex data structures.
For example:
context = {
"countries": [
{"name": "France"},
{"name": "Germany"},
{"name": "Japan"},
]
}You can access the name of the second country with:
<p>{{ countries.1.name }}</p>PyBlade first accesses index 1 of countries, then accesses the name key of the resulting dictionary.
The result is:
<p>Germany</p>You can continue chaining access as deeply as your data requires:
{{ customer.address.city }}
{{ countries.0.name }}
{{ orders.1.customer.name }}Negative indexes
Negative indexes work just like they do in Python. They let you access elements from the end of a list or tuple.
For example, -1 refers to the last element:
{{ countries.-1.name }}With our previous example, this produces:
JapanLikewise, -2 refers to the second-to-last element, -3 to the third-to-last element, and so on.
Good to know
Dot notation is a convenient alternative to bracket notation, not a replacement for it. You can still use regular Python-style indexing and key access whenever you need it:
{{ countries[0] }}
{{ customer["name"] }}
{{ countries[1]["name"] }}For dictionary keys, dot notation is available when the key is a valid Python identifier. Keys containing spaces, hyphens, or other unsupported characters must use bracket notation:
{{ my_dict["first-name"] }}
{{ my_dict["first name"] }}
{{ my_dict["user id"] }}Using filters on variables
PyBlade allows you to not only display variables but also transform them directly in templates using filters.
Instead of calling methods on objects like name.upper(), PyBlade uses a dot-based filter syntax.
What are filters ?
Filters are simple functions that accept a value and optionally additional arguments, and return a transformed value.
Filter Syntax
Filters are applied directly after a variable using dot notation. Arguments, if any, are placed inside parentheses:
{{ title.upper.truncate(20) }}This applies the upper filter first, then the truncate(20) filter on the result.
Filters can be chained in any order, and PyBlade will evaluate them left to right. This means expressions like user.name.upper.slugify work naturally.
Built-in filters
Below is a categorization of PyBlade’s built-in filters, organized by data type.
String & Text filters
| Filter | Description |
|---|---|
upper | Convert text to UPPERCASE |
lower | Convert text to lowercase |
title | Convert text to Title Case |
capitalize | Capitalize First character |
strip | Trim whitespace from both ends |
slugify | Convert to URL-friendly slug (lowercase, hyphens, no punctuation) |
truncate(length) | Truncate to a maximum of length characters |
Collection filters (Lists, Tuples, Dicts)
| Filter | Description |
|---|---|
length | Number of items in the collection |
first | First item or element |
last | Last item or element |
join(sep) | Join items into a string with separator |
Numeric filters
| Filter | Description |
|---|---|
add(x) | Add a number |
subtract(x) | Subtract a number |
multiply(x) | Multiply |
divide(x) | Divide |
Date & Time filters
| Filter | Description |
|---|---|
format(fmt) | Format a datetime according to fmt. Date format specifiers follow the standard Python strftime conventions (e.g. %Y for year, %m for month). |
humanize | Show relative time (e.g. "2 hours ago") |
Pro Tip
Keep template logic simple; heavy computations should occur in view code, passing only the final, display-ready data to your templates. This keeps templates focused solely on presentation, enhancing readability and performance.
PyBlade and JavaScript Frameworks
PyBlade is designed to work well with the rest of your frontend stack. You can use it alongside JavaScript frameworks and libraries such as React, Vue, Alpine.js, or any other tool that uses its own template syntax.
This can sometimes create a small problem: both PyBlade and your JavaScript framework may use curly braces to represent expressions.
For example, PyBlade uses:
<p>{{ name }}</p>But your JavaScript framework might also use {{ name }} for its own client-side expressions.
When this happens, you need a way to tell PyBlade:
This expression belongs to the frontend. Leave it alone.
Escaping a PyBlade expression
You can prefix the expression with @:
<div class="container">
Hello, @{{ name }}.
</div>When PyBlade processes this template, it removes the @ and leaves the expression untouched:
<div class="container">
Hello, {{ name }}.
</div>PyBlade does not evaluate {{ name }} in this case. The expression is left in the generated HTML so that the JavaScript framework responsible for that syntax can process it later.
This is useful when a template contains only a few expressions belonging to your frontend framework.
Ignoring PyBlade processing for a block
If a larger portion of your template contains client-side expressions, prefixing every expression with @ can quickly become repetitive.
For these situations, PyBlade provides the @verbatim directive. Everything between @verbatim and @endverbatim is passed through without being processed by PyBlade:
@verbatim
<div class="container">
Hello, {{ name }}.
</div>
@endverbatimThe resulting HTML will contain the original expression:
<div class="container">
Hello, {{ name }}.
</div>This allows your JavaScript framework to take full control of that section.
Choosing between the two
Use @{{ ... }} when you only need to escape an occasional expression:
<h1>{{ title }}</h1>
<!-- Handled by your JavaScript framework -->
<div>{{ clientSideValue }}</div>becomes:
<h1>{{ title }}</h1>
<div>@{{ clientSideValue }}</div>Use @verbatim when an entire section contains expressions that should not be interpreted by PyBlade:
@verbatim
<div>
{{ clientSideValue }}
{{ anotherValue }}
{{ computedValue }}
</div>
@endverbatimThis makes it easy to use PyBlade alongside JavaScript frameworks and other tools that have their own template syntax.
The @verbatim directive
The @verbatim directive tells PyBlade to leave everything inside the block untouched. PyBlade will not try to evaluate expressions or process directives contained within it.
This is useful when you need to include template-like syntax that belongs to another system, or when you simply want a section of your template to be passed through exactly as written.
For example:
@verbatim
<pre>
<code>
<p>{{ user.name }}</p>
@if(user.is_admin)
<span>Administrator</span>
@endif
</code>
</pre>
@endverbatimPyBlade will produce:
<pre>
<code>
<p>{{ user.name }}</p>
@if(user.is_admin)
<span>Administrator</span>
@endif
</code>
</pre>
Notice that neither {{ user.name }} nor @if is interpreted. The entire block is treated as plain content.
This is an important distinction: @verbatim does not only protect expressions from being evaluated. It also prevents PyBlade directives from being processed.
The @spaceless directive
When writing HTML, it is common to format your markup with indentation and line breaks to make the template easier to read:
<p>
<a href="foo/">Foo</a>
</p>Those line breaks and spaces are also present in the rendered HTML. If you don't need that whitespace, you can use the @spaceless directive to remove whitespace between HTML tags.
@spaceless
<p>
<a href="foo/">Foo</a>
</p>
@endspacelessPyBlade will render:
<p><a href="foo/">Foo</a></p>The directive removes whitespace between HTML tags, including spaces, tabs, and line breaks. It does not remove whitespace that is part of the text content itself.
For example:
@spaceless
<strong>
Hello
</strong>
@endspacelesswill render as:
<strong> Hello </strong>The whitespace around Hello remains because it is text content, not whitespace between HTML tags.
@spaceless is therefore useful when you want to keep your templates nicely formatted while avoiding unnecessary whitespace between the tags in the generated HTML.
Debugging
When developing a web application, debugging plays a crucial role in identifying issues and understanding how data flows within your templates. Whether you are troubleshooting missing data, unexpected outputs, or just trying to understand what’s available in your template, PyBlade provides an easy-to-use debugging directive.
The @debug directive
One of the simplest ways to gain insights into your template execution is by using the @debug directive. This directive prints a detailed breakdown of the current template context, helping you analyze the available variables and their values.
Using @debug is as simple as adding it inside your template:
@debugThis outputs a structured overview of the template context, helping you identify missing variables or unexpected values.
The @debug directive only works when debugging is enabled (DEBUG=True). In production (DEBUG=False), it outputs nothing, ensuring sensitive data remains hidden.
The @lorem directive
The @lorem directive generates random "lorem ipsum" text, which is commonly used as placeholder content in templates. This can be particularly useful when designing a template or layout, as it helps visualize how text will appear without needing to write out actual content. The generated text can either be a standard "lorem ipsum" or, when specified, random Latin words or paragraphs.
The @lorem directive can be used with up to three optional arguments:
@lorem([count], [method], [random])Here's a breakdown of each argument:
-
count:
The number of items (paragraphs or words) you want to generate. This can either be a fixed number or a context variable that holds the number. By default, it will generate one paragraph. -
method:
Specifies the type of content to generate. It can be one of the following:'w'for words: Will generate random Latin words.'p'for HTML paragraphs: Will generate full paragraphs wrapped in<p>tags.'b'for plain-text paragraph blocks: Will generate plain-text paragraphs without any HTML tags. This is the default option.
-
random:
If set toTrue, it will ensure that the generated content is random Latin text, instead of the usual standard "Lorem ipsum dolor sit amet..." paragraph. This adds variability in the generated text.For example, the following will output three paragraphs, each wrapped in
<p>tags, containing the standard "lorem ipsum" text.@lorem(3, 'p')
Comments
In PyBlade, comments allow you to include notes within your templates without rendering them in the final output. This is useful for adding explanations, reminders, or temporary code blocks without affecting the generated HTML.
To add a comment in PyBlade, wrap your comment text inside {# #} placeholders. Any content within {# #} will be ignored during rendering, so it won’t appear in the HTML output.
Example
<div class="content">
{# This is a comment and will not appear in the rendered HTML #}
<p>Welcome to our website!</p>
{#
Temporarily hiding this section
<p>Check back soon for updates.</p>
#}
</div>Output
<div class="content">
<p>Welcome to our website!</p>
</div>For convenience, PyBlade also provide a @comment directive for adding comments.
Sample usage:
@comment
<p>Commented out text</p>
@endcommentCongratulations!
You've now learned the basics of working with data in PyBlade: passing values from your Python application to a template, displaying them with {{ }}, providing fallback values, navigating nested data, and controlling how PyBlade processes your template.
But displaying data is only the beginning. As your templates become more dynamic, you'll need to make decisions, repeat sections, include other templates, handle empty states, and perform other common tasks directly in your markup.
Now let's take the next step. In the next section, we'll explore PyBlade Directives and learn how to add logic, structure, and reusable behavior to your templates.