If you write Jira tickets in Markdown, you've probably hit the same problem I did: the moment you paste that text into Jira, the result is inconsistent.
Sometimes you get raw **bold**, # headers, and pipe tables. Sometimes Jira partially formats things. Tables and code blocks usually need manual cleanup.
I built md2jira to make that workflow predictable.
What is md2jira?
md2jira is two things:
- A live web app โ paste Markdown on the left, get Jira-ready output on the right. No backend, no login, no data sent anywhere.
-
A framework-agnostic TypeScript package (
md2jira-core) โ use it in Node.js, CLI tools, or VS Code extensions.
๐ Live Demo: danielmontes9.github.io/md2jira
๐ GitHub: github.com/danielmontes9/md2jira
๐ npm: npmjs.com/package/md2jira-core
The problem in practice
Here's the difference between pasting raw Markdown directly into Jira and running it through md2jira first:
On the left, Jira gets raw Markdown and leaves it as plain text. On the right, the same content is converted into properly formatted Jira content with headings, emphasis, tables, nested lists, blockquotes, and code blocks.
The web app
The interface is a split panel:
- Left panel: a Markdown editor with line numbers, keyboard shortcuts (Ctrl+B, Ctrl+I, Ctrl+Shift+K, Alt+โโโฆ), import/export, and a "Copy MD" button
- Right panel: live output with two independent toggles โ format (Jira Cloud / Wiki Markup) and view (Preview / Code)
The "Copy for Jira" button writes rich HTML plus plain text to the clipboard, so when you paste directly into Jira Cloud it renders immediately without requiring manual cleanup.
What it converts
The core package currently handles:
-
# Headingโh1. Heading -
**bold**โ*bold* -
_italic_โ_italic_ -
~~strike~~โ-strike- -
`inline code`โ{{inline code}} - fenced code blocks โ
{code}/{code:language=...} - unordered and ordered lists
- links, blockquotes, horizontal rules, and tables
Tables were the hardest part. They needed special handling for multiline cells, unequal column counts, escaping pipes inside cells, and inline formatting inside the table content itself.
What turned out to be harder than expected
At first glance, Markdown-to-Jira sounds like a simple string replacement problem. It isn't.
The hardest parts were:
- Tables: Jira table syntax is strict, and real Markdown tables are often messy
- Clipboard behavior: Jira Cloud reacts differently depending on whether the clipboard contains plain text or rich HTML
- ADF vs Wiki Markup: Jira Cloud prefers richer structured content, while Jira Server/Data Center workflows often still rely on Wiki Markup
-
Keeping the core reusable:
packages/corehad to stay free of React and browser APIs so it can be reused in a future CLI and VS Code extension
The npm package
npm install md2jira-core
The package is a pure TypeScript library with zero browser or framework dependencies โ it works in Node.js, Deno, Bun, CLI tools, and VS Code extensions.
Basic usage
import { convert } from 'md2jira-core'
const jira = convert(`
# My Issue
Some **bold** text, _italic_, and ~~strikethrough~~.
| Field | Value |
|--------|-------------|
| Status | In Progress |
| Priority | **High** |
- Item 1
- Item 2
- Nested item
\`\`\`js
console.log("hello")
\`\`\`
> A blockquote
[Jira Docs](https://confluence.atlassian.com/jira)
`)
console.log(jira)
Output:
h1. My Issue
Some *bold* text, _italic_, and -strikethrough-.
|| Field || Value ||
| Status | In Progress |
| Priority | *High* |
* Item 1
* Item 2
** Nested item
{code:language=js}
console.log("hello")
{code}
bq. A blockquote
[Jira Docs|https://confluence.atlassian.com/jira]
ADF (Atlassian Document Format)
For Jira Cloud, you can also output ADF โ the native JSON format that Jira Cloud uses internally:
import { convertToAdf } from 'md2jira-core'
const adf = convertToAdf(`# Hello **World**`)
console.log(JSON.stringify(adf, null, 2))
The web app uses this output to generate the preview and the rich clipboard content used by the "Copy for Jira" action, which is what makes paste into Jira Cloud behave much better than plain text.
How it's built
The conversion pipeline is:
Markdown string
โ remark-parse (AST)
โ transforms/ (visit each node type)
โ Jira Wiki Markup string
Each node type (heading, paragraph, table, list, etc.) has its own transform function in packages/core/src/transforms/. The table transform handles the most edge cases โ multiline cells joined with <br>, missing cells padded with empty values, and inline formatting (bold, italic, links, code) inside cells.
The monorepo is structured so that packages/core is 100% framework-agnostic, making it reusable for a future CLI and VS Code extension.
Tech stack
| Layer | Technology |
|---|---|
| MD Parser |
remark-parse + unified
|
| AST types | @types/mdast |
| UI | React 18 + Vite + Tailwind CSS v4 |
| Testing | Vitest |
| Package manager | pnpm workspaces |
If you work with Jira regularly, give it a try โ feedback and PRs are very welcome!


Top comments (0)