Knap: a template language without eval, five export paths, and fixtures other languages can copy
A simple template language that turns data into Markdown.
At a glance
- What is it?
- Knap is a small template language that turns data into Markdown, built by the Obsidian team and already powering templating in Obsidian Web Clipper and Obsidian Importer. It parses templates into an AST and interprets them, so what a template can reach is decided by the host application rather than by the language.
- Who is it for?
- Knap suits anyone rendering Markdown from structured data inside a host application that wants to control what templates can reach, which is exactly the situation Obsidian's own clipper and importer are in. Before adopting it, check the runtime floor for your own path: the README states Node 20 or later for the CLI and the manifest declares no engines field at all, so a library consumer has to read the docs.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 43 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 13, 2026, and from our analysis. They are not legal advice.
Star history
- obsidianmd/knap669 ★ · +82
| Repository | 2026-09-21 | 2026-11-13 |
|---|---|---|
| obsidianmd/knap | 587 | 669 |
82 Stars in the last 53 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
Templates cannot reach anything the host did not expose
The central design decision is a refusal. Knap parses a template into an abstract syntax tree and interprets that tree, with no `eval` and no arbitrary JavaScript execution. The consequence is stated plainly: the application controls the data and the integrations available to templates. That is a real constraint on authoring. A template can only read variables the host supplies and apply filters the host registers, so a new capability is a change to the embedding application rather than a line of template code. The payoff is that a template coming from an untrusted source is data, not code, which is the situation a clipper or an importer is in by definition. Execution limits are documented alongside the API guide, alongside custom filters, asynchronous variables and HTML filters.
Five export paths, and a types map that repeats itself
The package manifest is unusually explicit about shape. It is an ES module with `sideEffects` set to false, built with tsup, and it publishes only four things: the `dist` directory plus README, CHANGELOG and LICENSE. Entry points come in pairs, with a CommonJS build and an ES module build for each: the root export, then `./html`, `./prism`, `./codemirror` and `./highlightjs`, which correspond to the editor adapters described later. Every one of those five paths carries its own types entry. A `typesVersions` block then maps the same four subpaths to their declaration files, which duplicates what the `exports` map already says; that is a compatibility shim for resolvers that predate conditional exports rather than a second source of truth. One binary is registered, `knap`, pointing at the compiled CLI.
Node 20 is stated for the CLI and declared nowhere
The runtime story is split across two places. The install section states that Node.js 20 or later is required for the CLI, and gives the consumer command `npm install knap`. The manifest, however, has no `engines` field, so npm will not warn a user on an older runtime at install time, and a library consumer reading only the package metadata gets no floor at all. That asymmetry matters most for the CLI path, since the bin is what people run from a shell. The command surface itself is narrow and is documented in its own guide. Rendering a template against JSON data is one invocation:
npx knap render template.md --data data.json --output note.mdAlongside it sit inline templates, validation, batch rendering, CSV input, and language help that works offline, so the tool can be driven without a network round trip.
Editor support is a build step, not an import
Two of the five integrations are artefacts you download rather than packages you install, and they are produced by a script in the repository. The manifest carries `editors:build` and `editors:check`, both running a Node script under the `editors/` directory, with the check variant intended to fail when generated files drift. What comes out is a VSIX for VS Code and Cursor, installed through the Extensions palette by choosing Install from VSIX, and a Sublime Text package dropped into the Installed Packages folder. Both are attached to the newest release. The other three are ordinary imports: Prism, CodeMirror 6 and highlight.js each take a companion install and then an adapter import from the matching subpath. CodeMirror 5 support is not in the manifest and lives in the editors guide instead, which is a reminder that the export list is not the whole compatibility story.
The example shows trimming, listing, and throwing
The library example is short enough to read as the specification. An engine is created with the standard filter set, then a template and a set of variables go into a call whose name says what happens on failure:
import { createEngine, standardFilters } from 'knap';
const engine = createEngine({ filters: standardFilters });
const markdown = await engine.renderOrThrow(
'# {{ title | trim }}\n\n{{ tags | list }}',
{
variables: {
title: ' An imported note ',
tags: ['reference', 'reading'],
},
},
);Three things are demonstrated at once. The title carries deliberate leading and trailing spaces and the `trim` filter removes them, so filter application is visible in the output rather than implied. The `list` filter turns an array into a rendering rather than printing an object. And the call is asynchronous and awaited, which lines up with the documented support for asynchronous variables, where a value is produced rather than looked up.
compat/fixtures exists for implementations in other languages
The repository carries a directory that no ordinary package needs. Under `compat`, a fixtures readme describes portable rendering fixtures: selected examples taken from the documentation, together with regression expectations, packaged so that implementations in other languages can reuse them. In practice that means the language is treated as a specification rather than only as a TypeScript library. Documentation examples double as conformance tests, and a second implementation can be held to the same rendering behaviour without reading the source. Two supporting directories sit alongside it. `bench/` holds benchmarks, and `tests/` holds the Vitest suite the `test` script runs. The consequence for a would-be implementer is that the fixtures, not the README, are the contract to satisfy.
npm for the consumer, pnpm for the repository
Two package managers, two roles. Installation for users is `npm install knap`, matching the public registry the package publishes to. Development inside the repository uses pnpm, with the root holding a pnpm lockfile and the two documented commands being `pnpm install` and `pnpm check`. That split is ordinary, but it means the lockfile you see does not describe the install path the README recommends to users. Release history shows the same briskness as the rest of the project: 0.5.0 published on 2026-09-13, then both 0.5.1 and 0.6.0 on 2026-09-14, two releases on one day. The last commit to the default branch came on 2026-10-02. The project sits at 0.6.0 under the MIT licence, with the author field naming a single maintainer.
Editorial conclusion
Knap suits anyone rendering Markdown from structured data inside a host application that wants to control what templates can reach, which is exactly the situation Obsidian's own clipper and importer are in. Before adopting it, check the runtime floor for your own path: the README states Node 20 or later for the CLI and the manifest declares no engines field at all, so a library consumer has to read the docs. Read the API guide's execution limits before letting users write templates, and remember that anything a template cannot do is something you have to expose as a filter or a variable. If you are writing the templating language rather than using it, the fixtures under compat are the part to read first.
Frequently asked questions
Does Knap execute JavaScript inside templates?
No. Templates are parsed into an AST and interpreted, with no eval and no arbitrary JavaScript execution. The host application decides which variables and integrations a template can reach.
What Node.js version does the Knap CLI need?
Node.js 20 or later, according to the install notes. The package manifest itself declares no engines field, so the floor is documented rather than enforced at install time.
Which editors have Knap syntax highlighting?
VS Code and Cursor through a knap.vsix installed with Install from VSIX, Sublime Text 4 through a Knap.sublime-package, and adapters for Prism, CodeMirror 6 and highlight.js imported from the matching subpath. CodeMirror 5 support is covered in the editors guide.
Can another language implement Knap templates?
Yes, that is what the fixtures under compat are for. Portable rendering fixtures reuse selected documentation examples and regression expectations so implementations in other languages can match the same behaviour.
Official sources
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.
[](https://hysenlabs.com/projects/obsidianmd-knap)Star history chart for your README
[](https://hysenlabs.com/projects/obsidianmd-knap)Was this useful?