md2rich

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.

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

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