Open-source project
yogthos/Selmer avatar
yogthos/Selmer

Selmer: a Django-flavoured template engine that compiles tags into a Clojure function

A fast, Django inspired template system in Clojure.

1,049 stars120 forksClojureEPL-1.0

At a glance

What is it?
Selmer is a pure Clojure template system whose files are compiled, with the tags replaced by the functions that handle them, and the result rendered against a context map. It borrows the Django filter and tag vocabulary, and its distinguishing practical features are a template cache keyed on file timestamps, configurable delimiters for pages that also carry a client-side framework, and a validator that reports the line of a bad tag.
Who is it for?
Selmer is worth adopting for a Clojure web application that renders HTML and wants a template language whose syntax its authors already know, since the Django vocabulary makes the filter list readable and the compiled design keeps rendering off the parser.
Can I use it commercially?
Yes, with conditions. EPL-1.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
Is it still maintained?
Yes. The repository last received commits 20 days ago.
What is it written in?
Mainly Clojure, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Templates are compiled once and rendered many times

Selmer is described as a fast, Django inspired template system in pure Clojure, and the first thing to understand is the two step model.

Selmer templates consist of plain text that contains embedded expression and filter tags. While Selmer is primarily meant for HTML generation, it can be used for templating any text. The critical line is what happens next: Selmer compiles the template files and replaces any tags with the corresponding functions for handling dynamic content, and the compiled template can then be rendered given a context map.

So a template is not parsed on every request. It is parsed once into something that is closer to a function, and rendering is the application of that function to a map. That is the design difference from a template engine that re-reads and re-parses on each render, and it is where the fast in the description comes from.

The API for that is small:

clojure
(use 'selmer.parser)

(render "Hello {{name}}!" {:name "Yogthos"})
=>"Hello Yogthos!"

and for a file on disk, render-file takes the same shape:

clojure
(use 'selmer.parser)

(render-file "home.html" {:name "Yogthos"})

There is also a string interpolation macro that injects symbols found in the environment directly into the template, which is the one piece of the API that is not a plain function call:

clojure
(let [a 1
      b "hello"]
  (<< "{{b|upper}}, {{a}} + {{a}} = 2"))

;;=> "HELLO, 1 + 1 = 2"

And a function to introspect a template without rendering it, known-variables, which returns the set of variable names a template declares:

clojure
(known-variables "{{name}}")
=>#{:name}

That last one is worth noting for anyone building tooling around templates, because it means a template can be asked what it needs rather than being rendered and having its missing values discovered by a validator.

The filter list is the Django list, which is the appeal and the ceiling

The README carries a long index of built-in filters, and recognising the list is the fastest way to understand the project's scope.

The filters include abbreviate, add, addslashes, block.super, capitalize, center, count, count-is, currency-format, date, default, default-if-empty, double-format, email, empty?, not-empty, first, take, drop, drop-last, get, get-digit, hash, join, json, last, length, length-is, linebreaks, linebreaks-br, linenumbers, lower, name, phone, pluralize, range, rand-nth, remove, remove-tags, safe, sort, sort-by, sort-by-reversed, sort-reversed, subs, sum, str, title, upper, urlescape, multiply, divide, round, between? and replace.

These are Django filter names doing Django filter jobs. pluralize takes a count and picks a word, safe marks a value as unescaped, json serialises a value, currency-format formats money, and remove-tags strips HTML from a value. A Django developer reading this list knows exactly what every one of them does, and that familiarity is the product.

The tags are the same story. There are block, comment, cycle, debug, if, ifequal, ifunequal, include, extends, firstof, for, now, safe, script, style, verbatim, with and embed.

That is a complete template language for server-rendered pages, and it is also the ceiling. There is no component, no partial that takes a slot with content, no loop macro that yields a body to a caller, and no escape hatch for a client-side framework beyond the custom markers described later. A Django team that has moved to template inheritance with blocks and includes will find the same shape here. A team that has moved to a component-oriented approach will find a gap, and the project's own positioning as Django inspired rather than Django equivalent is the honest description of where that gap sits.

The filter names are also Clojure-flavored in small ways, with json, hash, rand-nth and remove-tags being recognisable from the standard library, which is a sign the vocabulary was adapted rather than copied wholesale.

The cache keys on the rendered file, so includes and extends can go stale

This is the sharpest practical limitation in the project, and the README documents it in a section marked Important.

When rendering files, Selmer will cache the compiled template. A recompile is triggered if the last modified timestamp of the file changes. The caveat that follows is the one to know: changes in files referenced by the template will not trigger a recompile. If your template extends or includes other templates, you must touch the file that is being rendered for changes to take effect.

So the cache is keyed on one file, the one passed to render-file, and the templates it pulls in are not part of the key. During development that means editing a base layout does nothing until you touch the page that extends it. In production it means the same, which is a real operational hazard for a deployment where a base template is replaced independently of the pages that extend it.

The escape hatch is documented right after: caching can be turned on and off with selmer.parser/cache-on! and selmer.parser/cache-off! respectively. So the mitigation is available, at the cost of reparsing on every render, and it is a global switch rather than a per-template one.

The related mechanism is the resource path. By default the templates are located relative to the ClassLoader URL, and a custom location can be set with selmer.parser/set-resource-path!:

clojure
(selmer.parser/set-resource-path! "/var/html/templates/")

It is also possible to set the root template path relative to the application's own resource path, using a Clojure resource:

clojure
(set-resource-path! (clojure.java.io/resource "META-INF/foo/templates"))

That allows templates to be referenced with include and extends without specifying the full path, and passing nil resets the resource path back to the default. The README gives the deployment reason explicitly: this is useful when deploying the application as a jar and wanting to modify the HTML without redeploying. That is the case the resource path exists for, and it is also the case where the stale include problem is most likely to bite.

Custom delimiters exist because a Django-style template often shares a page with a client-side framework

The delimiter design is a small feature that reveals a lot about who the library is for.

By default, Selmer uses {% and %} to indicate the start and the end of an expression, while using {{ and }} for variables. The README says this might conflict with client-side frameworks such as AngularJS, and in that case you can specify custom tags by passing a map to the parser containing any of the following keys:

clojure
:tag-open
:tag-close
:filter-open
:filter-close
:tag-second

So the delimiters are a parser option rather than a global setting, and they are per-render rather than per-application, since the map is passed as an argument to render:

clojure
(render "[% for ele in foo %]{{[{ele}]}}[%endfor%]"
        {:foo [1 2 3]}
        {:tag-open \[
         :tag-close \]})
=>"{{1}}{{2}}{{3}}"

That example is worth reading closely, because it shows something specific about the marker design. The template uses square brackets for the tag delimiters, and the variable delimiters are left as {{ and }}, but the output comes out as {{1}}{{2}}{{3}} with the braces doubled, so the variable markers are escaped in the rendered result rather than being emitted as they were written.

The README does not spell out the rule behind that escaping in the visible text, so the safe reading is that a template author who changes the tag delimiters has to check what the variable and filter markers produce, and the :tag-second key is there for the case where a second form of tag syntax is needed, since the {{ }} convention has an inside and an outside form.

One more parsing rule is documented, and it is a classic gotcha. Namespaced keys such as :foo.bar/baz need the dot escaped, so the template variable is written with a doubled dot:

clojure
(parser/render "{{foo..bar/baz}}" {:foo.bar/baz "hello"})

The reason is that a single dot separates a namespace from a name in Clojure, so a key that itself contains a dot has to be distinguished from the namespace separator, and the doubling is the escape.

The validator reports the line and the tag, which is the useful part

Error handling in Selmer is unusually well specified, and it is the part of the project most likely to save you an afternoon.

Selmer will attempt to validate your templates by default, and validation can be disabled for any reason by calling selmer.validator/validate-off!. Whenever an error is detected by the validator, an instance of clojure.lang.ExceptionInfo is thrown.

The exception contains a defined set of keys, which means the error is machine-readable rather than a message you have to parse:

- :type, which is :selmer-validation-error - :error, the error message - :error-template, the error page template - :template, the template file that contains the error - :validation-errors, a vector of validation errors

Each error in the :validation-errors vector is a map with the details specific to that error, and those are :line, the line on which the error occurred, and :tag, the tag that contains the error.

The line number is the feature that matters. A template engine that reports a parse error without a line forces you to count lines by hand, and a large HTML file with a template in it is exactly the case where that is tedious. Having the line and the offending tag in the exception data means a wrapper can point at the exact place, and the :error-template key exists so the error page itself is a template, rendered the same way as anything else.

The README then introduces a middleware wrapper for that purpose, with a namespace declaration beginning myapp. The wrapper is the intended way to turn a validation error into a friendly error page, and the fact that the error page is itself a template means a broken template can also break the error page, which is worth keeping in mind when you set it up.

Where Selmer is the wrong tool

The limits are mostly about the age of the project and about what a template engine of this shape cannot do.

The first is the release history. There is a single release listed, 0.7.9 from 2015-01-09, and the last push was on 2026-10-03. The gap between them is more than a decade, and it means two different things. The code is still being worked on, and the repository is not archived, but the only release listed is from 2015. The documentation link points at version 1.13.4 on cljdoc, so the documentation is generated from a different, later version than the one release in the list.

The second is the cache behaviour, already described, and it is the flaw most likely to cost someone an afternoon. A template engine that recompiles only when the rendered file changes is fine for a single-page application and wrong for a site with a shared layout, which is the common case.

The third is the absence of a component model. Selmer is a text template engine with Django's vocabulary. It can include and extend, and it can loop, but there is no way to define a reusable component with a slot that a caller fills, which is the feature that most modern server-side rendering approaches are built around. A team that wants shared UI pieces with composition rather than inheritance has to build that on top.

The fourth is the escaping model, which is the one place where a Django-inspired engine inherits a Django assumption. The safe filter and the safe tag exist, and the readme mentions clientside frameworks such as AngularJS as a conflict case for the delimiters, which dates the design. A team that needs to output untrusted content safely needs to understand exactly what safe does in this implementation rather than assuming it matches Django's.

The fifth is scope of the language. There is no mention of streaming, no async rendering, and no partial that defers work. For a page that assembles several slow fragments, the whole page is rendered before it is sent, which is a design decision rather than a defect, but it is one to know about if that is your situation.

Editorial conclusion

Selmer is worth adopting for a Clojure web application that renders HTML and wants a template language whose syntax its authors already know, since the Django vocabulary makes the filter list readable and the compiled design keeps rendering off the parser. It is the wrong choice for a team that wants server-side rendering with a component model, since Selmer has no notion of a component, and the wrong choice for a project that must edit templates in production without a restart, because the cache keys on the rendered file's timestamp and not on the files it includes. Before adopting, read the filters and tags sections in the README, which are the actual API, and check the caching behaviour against how you deploy templates.

Frequently asked questions

What is Selmer and how does it render a template?

Selmer is a pure Clojure template system inspired by Django. It compiles template files, replacing the tags with the functions that handle dynamic content, and the compiled template is then rendered against a context map, using render for a string and render-file for a file.

How do I install Selmer?

It is published on Clojars, and can be added to a tools.deps project with the dependency {selmer/selmer {:mvn/version "<version>"}}, or pulled in with Leiningen. The generated documentation is on cljdoc.

Why are my changes to an included template not showing up?

Selmer caches the compiled template and recompiles only when the last modified timestamp of the rendered file changes. Changes in files referenced by the template will not trigger a recompile, so if a template extends or includes others you must touch the file being rendered. Caching can be turned off with selmer.parser/cache-off!.

Can I change the template delimiters?

Yes. The default is {% and %} for tags and {{ and }} for variables, and you can pass a map with :tag-open, :tag-close, :filter-open, :filter-close and :tag-second to render. The README suggests this when the page also carries a client-side framework such as AngularJS.

What does Selmer do when a template has an error?

Templates are validated by default, and a detected error throws a clojure.lang.ExceptionInfo with keys :type, :error, :error-template, :template and :validation-errors. Each entry in :validation-errors carries the :line the error occurred on and the :tag containing it, and the template under :error-template can be rendered as a friendly error page.

Official sources

  1. Issues
  2. License: EPL-1.0
  3. README
  4. Releases
  5. yogthos/Selmer on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/yogthos-selmer.svg)](https://hysenlabs.com/projects/yogthos-selmer)

Was this useful?