Skip to content

Search tags, filters, functions and guides.

Fast, safe templates for server-rendered websites.

Kiln is a template language with syntax you already know. Templates load their own data, output is escaped by default, and it runs on Node and Cloudflare Workers.

Get started
$ npm install @lucidkiln/core
  • Zero dependencies
  • Strict by default
  • MIT licensed
pages/product.kiln
{% set product = document("product", slug) %}

<h1>{{ product.name }}</h1>
<p>{{ product.price|formatNumber(style: "currency", currency: "GBP") }}</p>

{% island "components/stock" with { id: product.id } %}
	<p>Checking stock…</p>
{% endisland %}

How it works

Write a template. Get clean HTML.

Components take props and slots, loops know where they are, and data loads where it is used. Every example on this site is rendered by Kiln when the site builds.

page.kiln
{% set posts = documents("post", limit: 2) %}

{% for post in posts %}
	{% component "components/card" with {
		title: post.title,
		href: post.url,
		date: post.publishedAt,
	} %}
		<p>Post {{ loop.index }} of {{ loop.length }}</p>
	{% endcomponent %}
{% endfor %}
components/card.kiln
{% props title, href, date = null %}
<article class="card">
	<h2><a href="{{ href }}">{{ title }}</a></h2>
	{% if date %}
		<time>{{ date|formatDate(dateStyle: "long") }}</time>
	{% endif %}
	{% slot %}{% endslot %}
</article>
Output
<article class="card">
	<h2><a href="/blog/hello-world/">Hello, world</a></h2>
	<time>October 1, 2026</time>
	<p>Post 1 of 2</p>
</article>
<article class="card">
	<h2><a href="/blog/layouts/">Layouts in Kiln</a></h2>
	<time>October 8, 2026</time>
	<p>Post 2 of 2</p>
</article>

Compared

Built in, not bolted on.

Async data, islands, streaming and typed props come with Kiln. Here is how it compares with Twig and the most popular JavaScript template languages.

  • Has it
  • Partly. Hover for details
  • Doesn't have it
Feature Kiln Twig Nunjucks LiquidJS Handlebars
Components with named slots Yes Yes Partly: One slot No Partly: One slot
Props with types and defaults Yes Partly: Types aren't checked No No No
Safe for templates you didn't write Yes Partly: Opt-in sandbox No Yes Partly: Blocks prototypes
Async data Yes No Partly: Callback API Yes No
Streaming Yes Yes No Partly: Node streams only No
Server islands Yes No No No No
Runs on Cloudflare Workers Yes No Partly: Precompiled only Yes Partly: Precompiled only
Types generated from templates Yes No No No No
See the full comparison

Performance

Fast where it counts.

We render the same 7 real pages with 8 JavaScript template engines, and check every engine's HTML matches before timing. Kiln precompiles templates at build time, so it stays fast on Cloudflare Workers too.

faster than Nunjucks
5.4×
faster than LiquidJS
62×
faster than Twing
49×

Geometric mean across the pages, leaving out CMS data, which mostly measures waiting for data. Apple M4 Pro, Node 24.22.0.

See every result and how we measure

EngineRenders per second, higher is better

  • Kiln 113.4K
  • Pug 55.1K
  • Eta 50.2K
  • Handlebars 27.8K
  • Nunjucks 25.8K
  • EJS 20.1K
  • Twing 3.8K
  • LiquidJS 1.3K

A full article page: a layout with blocks, header and footer partials, trusted body HTML, related posts and twenty escaped comments.

Errors

Mistakes point straight at the problem.

Strict mode is on by default, so a typo is an error instead of a blank space on the page. Errors show where they happened and suggest what you meant.

Read about strict mode
KilnRenderError: Unknown variable "titel" in pages/page.kiln
at line 4, column 9. Did you mean "title"?

  3 | {% block content %}
> 4 |   <h1>{{ titel }}</h1>
    |          ^^^^^
  5 | {% endblock %}

Fire up your first page.

Install Kiln and render a page with a layout in a few minutes.