Martian turns Markdown into Notion blocks without a hand-rolled parser
Markdown to Notion: Convert Markdown and GitHub Flavoured Markdown to Notion API Blocks and RichText 🔀📝
At a glance
- What is it?
- Martian is a TypeScript library that parses Markdown and GitHub Flavoured Markdown into Notion API block and rich text objects, covering inline formatting, nested lists, tables, equations, images and three flavours of blockquote. It is MIT-licensed and targets Notion API version 1.0, though the published package versions have not moved since 2022.
- Who is it for?
- Martian is a good fit for a product that needs to push user-authored Markdown into a Notion page, particularly one with GFM alerts or callouts in its content, because the blockquote handling is the part you would otherwise spend a day on.
- 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 5 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 October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Two functions and a well-defined job
Martian is described as a Markdown parser that converts Markdown content to Notion API block or rich text objects, and the whole surface area is two exported functions. The intent behind the project is stated plainly: to make using the Notion SDK and API easier, against Notion API version 1.0. The mechanism is two-stage. It uses unified to build a Markdown AST, then converts that AST into Notion objects. Using a general purpose Markdown pipeline rather than a bespoke regex pass is the decision that matters here, because it means the parser inherits the same semantics as the rest of the unified ecosystem, and it means you can add plugins at the AST stage if you need to. The import surface is small enough to drop into a project without ceremony.
The two entry points and what rich text looks like
The package can be consumed from CommonJS or as a TypeScript import:
// JS
const {markdownToBlocks, markdownToRichText} = require('@tryfabric/martian');
// TS
import {markdownToBlocks, markdownToRichText} from '@tryfabric/martian';markdownToRichText handles a single line of inline content, and the example given in the README takes a bold phrase containing an italic word. The result is an array of rich text objects, each with an annotations object carrying bold, strikethrough, underline, italic, code and colour flags alongside the text content:
[
{
"type": "text",
"annotations": {
"bold": true,
"strikethrough": false,
"underline": false,
"italic": false,
"code": false,
"color": "default"
},
"text": {
"content": "Hello "
}
},
{
"type": "text",
"annotations": {
"bold": true,
"strikethrough": false,
"underline": false,
"italic": true,
"code": false,
"color": "default"
},
"text": {
"content": "world"
}
}
]The nesting works the way you would hope: the second segment is both bold and italic, because the two annotations compose rather than one overwriting the other. That is the kind of detail that separates a real AST conversion from a regular expression pass.
Three kinds of blockquote, and why the third is opt-in
The blockquote handling is the most substantial part of the library and the README spends the most space on it. Three types are supported. The first is the standard blockquote, which spans multiple lines. The second is the GFM alert form, where a marker line such as [!NOTE] or [!WARNING] precedes the content, and these are converted automatically into Notion callouts with the appropriate icon and background colour, with NOTE mapped to blue, TIP to green and the remaining alert types following the same pattern. The third is the emoji-style callout from ReadMe's Markdown callouts syntax, where the blockquote opens with an emoji that determines the colour. That third mode is optional and enabled with the enableEmojiCallouts option, and it also preserves formatting and nested blocks within the callout rather than flattening them.
Inline images are pulled out of their paragraph
Image handling contains a behaviour worth designing around. Inline images are extracted from the paragraph and added afterwards, and the README is explicit about why: inline images are not supported in Notion. So an image inside a sentence does not end up inside that sentence in the API output. The output ordering is still deterministic, since the images are appended after the paragraph content, but the association between a specific image and the words around it is lost. There is a second safeguard here: image URLs are validated, and if a URL is not valid according to the Notion external spec, it is inserted as text instead, so you can fix it manually. That fallback is a small kindness, since a rejected upload would otherwise fail the whole conversion.
The element coverage and the one lossy mapping
The supported list covers all inline elements, meaning italics, bold, strikethrough, inline code, hyperlinks and equations. Lists are supported in ordered, unordered and checkbox form, to any depth of nesting. Code blocks carry language highlighting support. Tables and equations are supported, and headers are supported with one lossy rule: header levels of three or deeper are all treated as header level 3, because Notion's block model has only three heading levels. The practical consequence is that a document with a deliberate h4 and h5 hierarchy comes out flat, and if that hierarchy matters to your reader you have to recover it from the text afterwards. The dependency list explains the parser configuration: remark-parse, remark-gfm for GitHub Flavour extensions, remark-math for the equation syntax, and unified as the processor, with @notionhq/client pulled in as a dependency of the package itself.
What the build and test setup says about the project
The package.json is small and conventional. The package is @tryfabric/martian, published publicly, and its entry points are the compiled build/src/index.js with matching type declarations. The test runner is jest with ts-jest, configured through jest.config.ts, and the linting is gts, the Google TypeScript style tooling, with eslint and prettier configuration in the tree. The compile step is a plain tsc, and a precompile script runs a languageMap script through ts-node, which is a hint that the mapping of fence languages to Notion code block languages is generated rather than hand-maintained. The prepare script runs the compile, and the test script runs a pretest compile and a posttest lint, so a commit is checked for both types and style. The engine requirement is node >=20.
Where Martian is the wrong choice
Three cases. The first is a bidirectional requirement, because Martian converts into Notion objects and the README describes no way to read a Notion page back into Markdown, so a tool that has to preserve an editable round trip needs something else. The second is a document that relies on Notion features the Markdown model does not have, such as synced blocks, columns, or database views, none of which a Markdown parser can produce. The third is a team that wants a maintained dependency, and here the release history is the concern. The most recent published versions are v1.2.4 and v1.2.3, both dated 2022-05-24, with v1.2.2 from 2022-05-11, while the last push to the repository was on 2026-10-18 and the last release in the list is v1.2.4. A repository can be alive while its published version sits still, so the practical advice is to pin a version, test against the exact Notion API version you use, and be ready to vendor the library if the Notion API moves. Against writing the conversion yourself with the Notion SDK, the difference in approach is that Martian has already made the awkward decisions about callouts, image placement and header flattening, and you inherit its choices along with its behaviour.
Editorial conclusion
Martian is a good fit for a product that needs to push user-authored Markdown into a Notion page, particularly one with GFM alerts or callouts in its content, because the blockquote handling is the part you would otherwise spend a day on. It is a poor fit for a project that needs Notion features the library does not model, such as syncing an existing page back to Markdown, and it is a poor fit for a team that needs a maintained dependency, since the latest published versions are v1.2.4 and v1.2.3 from 2022-05-24 even though the repository was pushed to on 2026-10-18. Check three things before you depend on it: that the Notion API version your integration uses still matches the version 1.0 shapes this library targets, that Node 20 or newer is available, since the package declares node >=20 as its engine, and that you read the inline image behaviour, because images are extracted from their paragraph and added afterwards since inline images are not supported in Notion, so any logic that expects a paragraph and an image to stay together will need to handle the reordering.
Frequently asked questions
What does the Martian npm package do?
It converts Markdown and GitHub Flavoured Markdown into Notion API block or rich text objects. It uses unified to create a Markdown AST and then converts that AST into Notion objects, and it is designed to make using the Notion SDK and API easier against Notion API version 1.0.
How do I import Martian in TypeScript?
The package exports two functions, markdownToBlocks and markdownToRichText. In TypeScript you use import {markdownToBlocks, markdownToRichText} from '@tryfabric/martian', and in JavaScript you use require('@tryfabric/martian') with the same two names.
How does Martian handle GFM alerts and blockquotes?
It supports three types: standard blockquotes, GFM alerts such as [!NOTE] and [!WARNING] which are converted automatically into Notion callouts with the appropriate icon and background colour, and optional emoji-style callouts enabled with enableEmojiCallouts, which preserve formatting and nested blocks.
What happens to images that appear inside a paragraph?
Inline images are extracted from the paragraph and added afterwards, because inline images are not supported in Notion. Image URLs are also validated, and a URL that does not meet the Notion external spec is inserted as text so you can fix it manually.
Which Markdown elements does Martian support?
All inline elements including italics, bold, strikethrough, inline code, hyperlinks and equations, plus ordered, unordered and checkbox lists to any depth, code blocks with language highlighting, block quotes, tables, equations and images. Header levels of three or deeper are all treated as header level 3.
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/tryfabric-martian)Was this useful?