templatel is the modern templating language that was missing for Emacs-Lisp. It provides variable substitution and control flow through a clean and powerful language inspired by Python's Jinja.

{% extends "base.html" %}
{% block title %}<h1>{{ title }}</h1>{% endblock %}
{% block body %}
    {% for page in pages %}
      <li><a href="{{ page.url }}">{{ page.title }}</a></li>
    {% endfor %}
{% endblock %}

Visit the API Reference for more details


Via Melpa

The simplest and recommended way to get yourself a copy of templatel is to install it via MELPA. You must have something along these lines on your Emacs configuration for that to work:

(add-to-list 'package-archives '("melpa" . ""))

And then run M-x package-install RET templatel RET. Notice that you might need to run M-x package-refresh-contents RET if you haven't done that in a while.


If you don't want to use any package installer, you can just clone the repository somewhere on your computer, let's say $HOME/src/templatel. And then you need to add that directory to your Emacs configuration so the library can be found by require.

(add-to-list 'load-path "~/src/templatel")

Project Status

Although templatel has been been functional since it's very first lil version, it's still too early to say it's stable. Until we reach 1.0, expect changes in the API as well as in the language itself. That being said, there will be an effort to keep compatibility with previous versions.

Differences from Jinja

There are a few things that won't ever be the same, because jinja was designed for Python and templatel is designed for Emacs Lisp. Most of these differences come out subtly like the rules for truthiness in the two host languages.

Templatel doesn't implement the following features:

  • Some built-in filters
  • For loop variables (loop.index, loop.first, loop.last, etc)
  • Whitespace Control
  • List and tuple literals
  • Else branch in for loops
  • Macros, Macro Calls and Import
  • Named Block End-Tags
  • Extensible Syntax
  • Autoescape Overrides
  • Assignments/Block Assignments
  • With Statement
  • Dictionary Syntax (access items with square brackets)
  • Line Statements

They are roughly sorted by how likely it would be for them to be implemented in templatel. Pull requests for making templatel more compatible to jinja are always welcome.



  • Default variables to undefined instead of raising an error
  • Added test operator is that is another syntax for passing parameters to functions that have a defined signature, they must return a Boolean value. e.g.: {% if something is defined %}.
  • Support {% include %} statements (Thanks to Guilherme Guerra)
  • New tests added: defined, divisible
  • New filters added: take (Thanks to Marko Kocić), abs, capitalize, escape (e), float, join, length, max, min, round, and title. Documentation is still ongoing, and help is wanted :)
  • Filter deprecated: getattr, please use attr instead
  • fix: for loop must access expression via value stack [commit]
  • fix: parsing floating point numbers [commit]
  • fix: filter operator precedence [commit]
  • fix: operators for remainder of division (%) and exponential ** implemented. They were present in the syntax but didn't have an implementation


  • New filters added: first, last, getattr and default
  • New API for removing a template from an environment by name: templatel-env-remove-template
  • Fix inheritance so both extends and super() work recursively. E.g.: post.html extends base_post.html that extends base.html.
  • Render nil value as an empty string instead of inserting the "nil" string into the output file


  • Add support for automatic HTML entity escaping
  • Fix reading UTF-8 characters with more than one byte
  • Documentation overhauling


  • Support for named arguments on filters. They are passed to the handler as an assoc list. E.g.: {{ foo(var1="val", var2=2) }} will get ((var1 . val) (var2 . 2)) as the only parameter. Named and positional arguments can be mixed, but the Emacs-Lisp handler will have to deal with the order.


  • Support for user defined filter functions via templatel-env-add-filter and templatel-env-remove-filter.
  • Fix parsing expressions and statements without spaces after brackets, e.g.: {{var}}, {%if stuff%}
  • Parse and evaluate standalone function calls. E.g.: {{ super() }}
  • Breaking change: super() is now a function, not a variable anymore. But still only exists while blocks are evaluated


  • Fix elif without else statements
  • Make templatel-env-{source,run-importfn} private
  • Pretty big refactoring cleaning up the symbol names
  • Documentation of the project was kicked off