Development recommendations
November 4, 2025 ยท View on GitHub
Before continuing, make sure you've read:
READMEfor package-specific notes- markdown-it README
- API documentation
- Architecture description
General considerations for plugins.
- Try to find the right place for your plugin rule:
- Will it conflict with existing markup (by priority)?
- If yes - you need to write an inline or block rule.
- If no - you can morph tokens within core chains.
- Remember that token morphing in core chains is always more simple than writing block or inline rules, if you don't copy existing ones. However, block and inline rules are usually faster.
- Sometimes, it's enough to only modify the renderer, for example, to add
header IDs or
target="_blank"for the links. - Publish plugins as ESM-first packages when possible.
markdown-it-tsexposes the same runtime contract as markdown-it but is authored in TypeScript, so shipping type definitions (typesorexportsfield) makes the developer experience much better. - Plugins should not require the
markdown-itpackage as dependency inpackage.json. If you need access to internals, those are available via a parser instance passed on plugin load. See properties of the main class and nested objects. - Prefer shipping both
markdown-itandmarkdown-it-tsas peer dependencies if you surface shared helpers, so consumers can choose the runtime.
- Search existing plugins or rules, doing something similar. It can be more simple to modify existing code, instead of writing all from scratch.
- If you did all steps above, but still has questions - ask in
tracker. But, please:
- Be specific. Generic questions like "how to do plugins" and "how to learn programming" are not accepted.
- Don't ask us to break CommonMark specification. Such things should be discussed first on CommonMark forum.
Notes for publishing packages
To simplify search and TypeScript consumption:
- add to
package.jsonkeywordsmarkdown-itandmarkdown-it-pluginfor plugins. - add keyword
markdown-itfor any other related packages. - point
types(and optionallyexportssubpath types) to your declaration files somarkdown-it-tsusers inherit typings automatically.
FAQ
I need async rule, how to do it?
Sorry. You can't do it directly. All complex parsers are sync by nature. But you can use workarounds:
- On parse phase, replace content by random number and store it in
env. - Do async processing over collected data.
- Render content and replace those random numbers with text; or replace first, then render.
Alternatively, you can render HTML, then parse it to DOM, or cheerio AST, and apply transformations in a more convenient way.
How to replace part of text token with link?
The right sequence is to split text to several tokens and add link tokens in between.
The result will be: text + link_open + text + link_close + text.
See implementations of linkify and emoji - those do text token splits.
Note. Don't try to replace text with HTML markup! That's not secure.
Why my inline rule is not executed?
The inline parser skips pieces of texts to optimize speed. It stops only on a small set of chars, which can be tokens. We did not made this list extensible for performance reasons too.
If you are absolutely sure that something important is missing there - create a ticket and we will consider adding it as a new charcode.
Why do you reject some useful things?
We do a markdown parser. It should keep the "markdown spirit". Other things should be kept separate, in plugins, for example. We have no clear criteria, sorry. Probably, you will find CommonMark forum a useful read to understand us better.
Of course, if you find the architecture of this parser interesting for another type of markup, you are welcome to reuse it in another project. The TypeScript codebase is intentionally modular and can be used as a foundation for other DSLs or custom Markdown dialects.