Knap vs md2rich 2026: Data to Markdown Two Ways
Two tools, one phrase in common — "data into Markdown" — and almost nothing else. Knap is a template language that writes Markdown for you from JSON and CSV. md2rich converts Markdown you already wrote into rich text you can paste. Knowing which one you need depends entirely on which end of the file you are stuck at.
What Knap actually is
Knap showed up on Hacker News on 2026-09-10 under the title "Knap – Turn Data into Markdown," and it is easy to file it next to the dozens of paste-and-convert tools that have appeared this year. That would be a mistake. Knap is not a converter with a text box. It is a small templating language, and its own documentation is careful about the wording: "Knap is a simple template language that turns data into Markdown using logic, variables, and filters."
The project is maintained by Obsidian — the repository is github.com/obsidianmd/knap, MIT licensed, first created on 2026-08-21. It started as the templating language behind Obsidian's own tools and was then released for anyone to use. As of this writing the repo sits at 140 stars with the last push on 2026-09-10, so it is young and under active development rather than parked. The npm package is knap, currently at 0.4.1, with a single dependency (dayjs) and a stated requirement of Node.js 20 or later.
Where you have probably already met it without knowing: the Obsidian Web Clipper uses Knap variables to standardize the content it pulls off web pages, and the Obsidian Importer plugin uses it to convert data out of other apps and file formats into portable Markdown files. That is the clearest statement of what Knap is for. It is the machinery that turns a messy, structured input into a tidy Markdown file, repeatedly, without a human retyping the structure each time.
The core idea: a template plus a record
Knap has three moving parts, and the documentation names them plainly: variables, filters, and logic.
- Variables are written in double braces —
{{ title }}— and are replaced with values from your data when the template renders. - Filters follow a pipe and run left to right, so
{{ title | h2 | upper }}produces## THE MACHINE STOPS. There are dozens of them, and many are Markdown-aware:blockquote,table,list,wikilink,yaml_property,code,math. - Logic uses brace-percent tags for conditions and loops —
{% if %},{% for %}, plus comparison operators and a??fallback.
Here is the smallest useful example, straight from the docs. A template file, and the Markdown it produces:
# {{ title }}
**Plot:**
{{ plot | blockquote }}
Given a data object where title is "The Matrix" and plot is a sentence about a hacker discovering a simulation, the output is:
# The Matrix
**Plot:**
> A hacker discovers that reality is a simulation and joins a rebellion against its machines.
That is the whole pitch. The blockquote was never typed — the filter produced it. Scale that up to a hundred records and you have the reason this exists.
Where the structural filters earn their keep
The examples that make Knap worth a second look are the ones that would be tedious to do by hand. A list of cast members becomes a Markdown table when you pipe it through the table filter, and the filter handles sorting on its own:
{% if cast %}
{{ cast | sort:"Actor" | table }}
{% endif %}
Which renders as a real, aligned Markdown table with an Actor and Role column — the header row included, the delimiters correct, the rows sorted. If you have ever hand-built a Markdown table inside a template string and gotten the pipe counts wrong on row nine, you understand why that is a feature and not a convenience.
Knap also understands the parts of Markdown that are not really Markdown — the YAML frontmatter and the app-specific extensions. It can emit frontmatter and Obsidian-style wikilinks directly:
---
year: {{ year }}
{{ directors | wikilink | yaml_property:"directors" }}
---
That produces a frontmatter block with a directors property whose values are wikilinks — the exact shape Obsidian expects, generated rather than remembered. For anyone maintaining a vault of imported notes, that is the difference between a script and a ritual.
How you actually run it
Knap runs in two places: the terminal and your app.
In the terminal, the CLI comes with the npm package. You can render an inline template with no files at all, or render a template against a JSON data file:
npx knap render -t '# {{ title }}' --set title=Hello
npx knap render template.md --data data.json --output note.md
There is also a validation step, which is the detail that tells you this is meant for automation rather than a one-off paste. knap validate template.md checks syntax, filter names, and statically checkable filter arguments, exits 0 on success and 1 on failure, and writes nothing. It does not render the template or check that your variables exist — the docs are explicit about that limit — but it catches the class of error you would otherwise only find at the end of a batch. For repeated generation from records, there is batch rendering, which produces one file per record from a single template and a data set.
In an app, you install the same package and build an engine:
import { createEngine, standardFilters } from 'knap';
const engine = createEngine({ filters: standardFilters });
const result = await engine.render('# {{ title | trim }}', {
variables: { title: ' An imported note ' },
});
The execution model is worth noting if you are considering it for anything shared: Knap parses templates into an AST and interprets them without using eval or executing arbitrary JavaScript, and engines apply finite default limits on template length, output length, operations, and nesting depth. It is a template engine designed by people who expect it to be pointed at untrusted input.
The fork in the road: generating vs publishing
This is where the briefing comparison and the honest comparison diverge. Putting Knap next to md2rich as two "data to Markdown" tools describes a competition that does not exist. They meet at a file, from opposite directions.
Knap writes Markdown. You give it structured data and a template; it gives you a .md file. The file did not exist as prose before you ran the command. The tool's job is to make the repetitive parts of structure automatic.
md2rich reads Markdown you already wrote. You give it a finished draft; it gives you rich text you paste into LinkedIn, X Articles, Medium, or Notion. The prose existed; the tool's job is to make sure the platform does not destroy its structure on the way in.
Put another way: Knap is authoring by template, md2rich is publishing by conversion. One runs at build or import time on a developer's machine, the other runs at publish time in a browser. Those are different moments in the life of a document, and the tool that solves one does not solve the other.
Two real Markdown-to-rich-text examples
The gap between generating Markdown and publishing Markdown is easiest to see when the generated Markdown gets pasted somewhere that cares about structure. Take a record rendered by a Knap-style template into a note with a heading, a task list, and a table:
# Q3 Release Notes
## Shipped
- [x] Search index rebuild
- [x] Export to Notion
- [ ] Mobile paste fix
## API limits
| Plan | Requests/day |
| - | - |
| Free | 1000 |
| Pro | 50000 |
Generated, that file is correct. Pasted raw into a rich-text editor, the task list can flatten into lines of literal - [x], and the table can collapse into a paragraph of pipes and dashes. That is the moment md2rich exists for: it converts the Markdown source client-side so the heading stays a heading, the task list stays a task list, and the table stays a table when it lands.
Second example, the announcement post itself — the piece people paste badly most often. A short file with a heading, a code block, and a link:
# We moved our notes to Markdown
Templates generate the boring parts; humans write the sentences.
Plan: Free -> Pro
Records: 412
Read the migration notes before you import.
Both convert through md2rich entirely client-side — the site is a pure front-end tool with zero upload. That property matters more, not less, in a pipeline like this one. If your source data is internal — customer records, an internal CSV, a private vault — the generating step happens locally with Knap and the publishing step happens locally with md2rich. The only thing that ever becomes public is the page you deliberately published.
Where Knap does not fit yet
- It is three weeks old and at 0.4.x. The repo was created 2026-08-21. The template syntax is stable enough to be worth learning, but expect the pre-1.0 churn that comes with a new language and do not treat the filter list as frozen.
- It is a language, so it has a learning curve. If you need to convert one document once, opening an editor and writing it is faster than writing a template to generate it. Knap pays off on repetition, and only on repetition.
- Validation is static, not semantic.
knap validatechecks syntax and filter names but does not check whether your variables exist or inspect runtime values. A template that validates can still render empty. Render with real data before trusting a batch. - Node.js 20 or later is required. Not a problem for most machines, but it is a hard floor if any part of your pipeline is pinned to an older runtime.
- The CLI is a subset of the API. DOM-dependent HTML filters and custom app integrations are available through the library API, not the command line. If your template needs those filters, you are writing code, not running a command.
- It does not solve the paste problem. Nothing about generating Markdown from a template tells a rich-text editor how to accept it. That is a different layer, and it is the one md2rich occupies.
The bottom line
If you maintain hundreds of Markdown files that all share a structure, or you regularly turn spreadsheets and API responses into notes, tables, and frontmatter, Knap is worth the afternoon it takes to learn. It is MIT licensed, it is maintained by Obsidian, and it is confidently solving a problem that most "Markdown tools" ignore entirely — the generation of structure, not its conversion. Check whether your repetitive Markdown is repetitive enough to template; if it is, this removes the work at the source.
If your recurring pain is the last mile — a finished draft that arrives in LinkedIn or X Articles as an unformatted wall of text, tables collapsed into pipes, code blocks flattened into paragraphs — a template language will not help you. There is no data to template from, because you wrote the prose by hand. That is what md2rich does, and it does it in the browser with nothing uploaded.
Most people who publish a lot end up with both, because the two problems live at opposite ends of the file. Generate the draft with Knap when the structure is repetitive. Convert it with md2rich when it is time to paste it into LinkedIn, X Articles, Medium, or Notion. The Markdown between the two steps can stay on your machine the whole way.
Template the structure. Convert the draft.
Knap generates Markdown from your data; md2rich turns the finished Markdown into rich text for LinkedIn, X Articles, Medium, and Notion — all in your browser, nothing uploaded.
Try md2rich