# Knap Knap is an open source template language that **turns data into [[Markdown]]**. You write a template with variables, filters, and logic, feed it data (JSON, CSV, whatever your app has), and get Markdown out. It was created by [[Steph Ango]] (kepano), CEO of [[Obsidian]], and released on September 10, 2026 under the MIT license. If you've used [[Obsidian Web Clipper]] templates, you already know Knap. It started as the templating language of the Web Clipper, and it also powers the Obsidian Importer. According to Steph, over a million people already use it directly or indirectly. Now it's a standalone project that any tool can use. The name is pronounced /knæp/, with a hard k. It comes from *knapping* (silent k): shaping stones into arrowheads, scrapers, and other tools. Nice one 😉 ## Why this matters Markdown is the format everything speaks now: note-taking apps, static sites, docs, and LLMs. Generating it cleanly from data is a very common need, and until now, most people glued strings together by hand or pulled in a general-purpose engine that knows nothing about Markdown. Knap is built for Markdown specifically. Its filters produce wikilinks, callouts, tables, YAML properties, footnotes, and more. And it's safe to embed: templates are parsed into an AST and interpreted, **without `eval` and without running arbitrary JavaScript**. The syntax will feel familiar if you know Liquid, Jinja, or Nunjucks. ## The three building blocks The official site color-codes the syntax to help beginners: variables in blue, filters in orange, logic in green. ### Variables Variables go between double braces: ``` {{ title }} {{ author.name }} {{ authors[0].name }} {{ metadata["article:section"] }} ``` - Dot notation for object properties - Bracket notation for array indexes or keys with special characters - Bracket expressions can use another variable, handy to read two arrays in parallel - Values can be strings, numbers, booleans, arrays, objects, or null ### Filters Filters transform a value using the pipe operator. They chain from left to right, and some take parameters: ``` {{ title | h2 | upper }} {{ plot | blockquote }} {{ tags | wikilink | list }} {{ published | date:"YYYY-MM-DD" }} ``` There are MANY built-in filters, grouped by purpose: - **Formatting** (Markdown structures): `blockquote`, `bold`, `italic`, `strike`, `highlight`, `callout`, `code`, `code_block`, `comment`, `embed`, `escape_md`, `footnote`, `fragment_link`, `h1` to `h6`, `hard_break`, `hr`, `image`, `link`, `list`, `math`, `math_block`, `table`, `table_pretty`, `wikilink`, `yaml`, `yaml_property` - **Text**: `camel`, `capitalize`, `decode_uri`, `encode_uri`, `indent`, `kebab`, `lower`, `pascal`, `replace`, `safe_name`, `snake`, `title`, `trim`, `truncate`, `truncatewords`, `uncamel`, `unescape`, `upper` - **Dates**: `date`, `date_modify`, `duration` (seconds or ISO 8601 durations) - **Numbers**: `calc`, `number_format`, `round` - **Collections**: `compact`, `first`, `last`, `join`, `length`, `map`, `merge`, `nth`, `object`, `parse_json`, `reverse`, `slice`, `sort`, `split`, `sum`, `template`, `unique`, `where` - **HTML cleanup**: `remove_attr`, `remove_tags`, `replace_tags`, `strip_attr`, `strip_md`, `strip_tags` - **HTML parsing** (from `knap/html`, needs a DOM): `html_to_json`, `remove_html` A few favorites for [[Obsidian]] users: `wikilink` for links, `callout` for callouts, `yaml_property` for frontmatter, and `safe_name` for file names. ### Logic Logic tags use `{% ... %}`. They control what gets rendered and produce no output themselves. **Conditions**: ``` {% if rating >= 4 %} Recommended! {% elseif rating %} Rated {{ rating }}/5 {% else %} Not rated yet {% endif %} ``` - Comparison: `==`, `!=`, `>`, `<`, `>=`, `<=` - `contains` checks substrings or array membership - Logical operators: `and`/`&&`, `or`/`||`, `not`/`!`, plus parentheses for grouping - Falsy values: `false`, `null`, `undefined`, empty string, `0`, and empty arrays **Fallbacks** with `??`, which returns the first truthy value: ``` {{ subtitle ?? "No subtitle" }} ``` Keep in mind that `0` and `false` also trigger the fallback, and filters run before `??`. **Loops**: ``` {% for tag in tags %} - {{ loop.index }}. {{ tag }} {% endfor %} ``` Inside a loop, you get `loop.index` (1-based), `loop.index0` (0-based), `loop.first`, `loop.last`, `loop.length`, and a named index like `tag_index`. **Assignments** with `set`, to store a value and reuse it later in the template. **Comments** with `{# ... #}`. They're removed from the output, and nothing inside them gets evaluated. They can span multiple lines but don't nest. ## The CLI Install with `npm install knap` (or pnpm, yarn, bun). The CLI needs Node.js 20+. **`render`**: render one template. ```bash npx knap render -t '# {{ title }}' --set title=Hello knap render template.md --data data.json --output note.md cat data.json | knap render template.md --data - --output note.md ``` Options: `--template`/`-t` (inline template), `--data`/`-d` (JSON file, or `-` for stdin), `--data-json` (inline JSON), `--set` (override a variable, repeatable), `--output`/`-o` (file, or `-` for stdout). **`batch`**: generate MANY files at once. One file per CSV row, per object in a JSON array, or per JSON file in a folder. ```bash knap batch template.md --data articles.csv --output-dir notes \ --filename '{{ title | safe_name }}.md' ``` Options: `--data-json`, `--format` (`csv` or `json`), `--filename` (a template for file names), `--overwrite`, `--dry-run`. Everything is prepared in memory before anything gets written. **`validate`**: check syntax, filter names, and filter arguments before rendering. ```bash knap validate template.md knap validate -t '{{ title | upper }}' ``` **`help`**: offline docs in the terminal: `knap help syntax`, `knap help filters`, `knap help filter date`, `knap help tags`, `knap help tag for`. This is where things get interesting: you can pipe [[Defuddle]] into Knap to turn any web page or URL into a clean, templated Markdown note. That's basically the Web Clipper pipeline, from the command line. Combined with `batch`, you can turn a CSV export into hundreds of notes in one command. ## The JavaScript API ```javascript 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'], }, }, ); ``` The key pieces: - `createEngine({ filters, allowRegex, limits })`: the filter registry is immutable, and `allowRegex` controls whether `split`/`replace` accept regular expressions - `render()` returns `{ output, errors, warnings }`. `renderOrThrow()` throws a `TemplateRenderError` instead (warnings never throw) - `resolveVariable` loads variables lazily (and asynchronously). A `context` object passes host data to filters and resolvers without exposing it to the template - `engine.validate()` checks a template without rendering it, and `parse()` returns the AST. Useful for editors - **Custom filters** can be sync or async. `FilterContext.rawValue` gives access to the original typed value - **Execution limits**: `maxTemplateLength` (1M characters), `maxOutputLength` (5M), `maxValueLength` (5M), `maxOperations` (100k), `maxDepth` (100, up to 256) - Errors carry a stable `code`, `message`, `line`, and `column` Two caveats worth knowing: - Limits are NOT a sandbox. For untrusted templates, run Knap in a worker you can kill, with a wall-clock deadline - HTML filters don't sanitize. Sanitize the output before inserting it into a web page ## My take I love this kind of project. It takes something that was locked inside one product, polishes it, documents it well, and gives it to everyone. That's very much in line with Obsidian's "file over app" philosophy (see also [[JSON Canvas]] and [[Defuddle]]). For Obsidian users, the practical benefit is simple: one template syntax to learn, usable in the Web Clipper, in scripts, and in your own tools. Try the playground, you'll get it in a few minutes. ## References - Official website: https://knap.md - Playground: https://knap.md/playground - Variables: https://knap.md/variables - Filters: https://knap.md/filters - Logic: https://knap.md/logic - CLI: https://knap.md/cli - API: https://knap.md/api - Source code: https://github.com/obsidianmd/knap - Announcement by Steph Ango: https://x.com/kepano/status/2098126579826377066 ## Related - [[Steph Ango]] - [[Obsidian]] - [[Obsidian Web Clipper]] - [[Defuddle]] - [[JSON Canvas]] - [[Markdown]] - [[Obsidian Flavored Markdown]] - [[DataMark]] - [[Templater plugin for Obsidian]]