Websauna uses Jinja template language as its default template engine for rendering HTML. You are free to choose any other template engine for your templates. See Pyramid support for different template engines.

How templating works

URLs are never directly connected to template files. Instead, URLs point to views. Views prepare the data as input for the page template. This usually includes doing database look ups and processing form posts. A view returns a dictionary which contains items which can be later substituted in template using {{ variable }} syntax.

Read how views work.

Template reference

See template reference to get yourself familiar with templates supplied with Websauna.

Jinja primer

To learn how Jinja template language works read Jinja Template Designer Documentation.


Base templates consist of blocks which you can fill in.

See template reference for available blocks.

Filters and context variables

See template content variable and filter reference to get yourself familiar with Jinja variables and filters supplied with Websauna.

Template loading order

Template loader in set up in such a way that it tries to

  • Load the first matching template relative to the templates folder
  • Load template from your Websauna myapp/templates folder
  • Load from any Websauna addon
  • Load from Websauna core package

For example it would first try myapp/templates/site/logo.html and then websauna/system/core/templates/site/logo.html.

All templates folder are connected to the Jinja template loader in websauna.system.Initializer. See

Rendering a template

Rendering for a view

The template is usually rendered by returning a template context dictionary from a view function. The template context dictionary is passed to a template defined by renderer parameter in the view config. renderer must be a path to a file defined in one of the template paths.


from websauna.system.http import Request
from websauna.system.core.route import simple_route

@simple_route("/", route_name="home", renderer='myapp/home.html')
def home(request: Request):
    """Render site homepage."""
    project_name = "Mikko's awesome cow hiphop music videos"
    return locals()

Then you can have a template:

{% extends "site/base.html" %}

{% block content %}
    Welcome to {{ project_name }}
{% endblock %}

Manual rendering

You can manually render a template by calling pyramid.renderers.render. Example:

from pyramid.renderers import render

def my_utility_function(request, first_name, last_name):
    output = render("hello_world.txt", dict(first_name=first_name, last_name=last_name), request=request)

Alternatively if you know the output will be a HTTP response you can use pyramid.renderers.render_to_response:

from pyramid.renderers import render_to_response

def my_view(request):
    return render_to_response("hello_world.html", dict(first_name="Mikko", last_name="Ohtamaa"), request=request)


Variables in URLs

Below is an example how to use template variable in static_url:

{% set img=product_description.images.0 %}
{% set full_img='trees:static/theme/img/product/' ~ img  %}

<div id="product-page-header" style="background-image: url({{ full_img|static_url }});">
    <!-- Use product image as the background image for the page -->


Static images

The usual process to add an image on your website is

  • Include image file in static folder of your application
  • Refer to this image using static_url filter in your template.


<img src="{{ 'myapp:static/assets/img/logo-transparent.png'|static_url }}" alt="">

Customizing navigation

Navigation is defined in site/nav.html.

Copy nav.html file to yourapp/site folder.

Edit the file and add new entries to navbar-collapse section.


<nav class="navbar navbar-default">
  <div class="container">
    {# Brand and toggle get grouped for better mobile display #}
    <div class="navbar-header">
      <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#header-navbar-collapse">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      {% include "site/logo.html" %}

    <div class="collapse navbar-collapse" id="header-navbar-collapse">
      <ul class="nav navbar-nav navbar-left">
        <li class="hidden">
          <a href="#page-top"></a>

          <a href="{{'invoices'|route_url}}">Bills</a>

          <a href="#">Top up</a>

          <a href="#">Send money</a>

          <a href="#">Withdraw</a>

      <ul class="nav navbar-nav navbar-right">
            {# .... #}
    {# /.navbar-collapse #}
  {# /.container-fluid #}

Formatting decimals

Jinja can use Python string formatting:

Price: <strong>${{ '{0:0.2f}'.format(price) }}</strong>

Alternative use round where you can give rounding direction:

Price: <strong>${{ price|round(precision=2, method='common') }}</strong>


Invoking debugger inside templates

You can start a Python debugger prompt, pdb or any of its flavour, inside a page template. This allows you to inspect the current template rendering context, variables and such.

If you put into the template

<h1>Template goes here</h1>

{{ debug() }}


Next time you reload the page the command line debugger will open in your ws-pserve terminal.

Now you can inspect template context.

>>> up
... -> return __obj(*args, **kwargs)
>>> up
-> <li>
>>>  context.keys()

dict_keys(['js_in_head', 'site_email_prefix', 'lipsum', 'render_flash_messages', 'view', 'dict', 'site_tag_line', 'on_demand_resource_renderer', 'joiner', 'site_url', 'panel', 'site_author', 'debug', 'context', 'renderer_info', 'ngettext', 'site_time_zone', 'range', 'request', '_', 'site_name', 'req', 'cycler', 'panels', 'gettext', 'renderer_name'])

>>> context["request"].admin.get_admin_menu().get_entries()

ValuesView(OrderedDict([('admin-menu-home', <websauna.system.admin.menu.RouteEntry object at 0x112b74ba8>), ('admin-menu-data', <websauna.system.admin.menu.RouteEntry object at 0x112b74b38>)]))

See debug and websauna.template_debugger for more information.

See more information in template debugging article.

Accessing Jinja environment

Each template suffix (.txt, .html, .xml) has its own Jinja environment.


from pyramid_jinja2 import IJinja2Environment

def find_filters(request):
    env = request.registry.queryUtility(IJinja2Environment, name=".html")
    filters = []
    for name, func in env.filters.items():
        print(name, func)