DEV Community

Daniel Montes
Daniel Montes

Posted on

Stop pasting broken Markdown into Jira: building md2jira

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:

  1. A live web app โ€” paste Markdown on the left, get Jira-ready output on the right. No backend, no login, no data sent anywhere.
  2. 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:

Before: raw markdown pasted into Jira โ€” no formatting applied

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

md2jira-previewer โ€” live two-panel converter in the browser

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/core had 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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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!

๐Ÿ”— Live Demo ยท GitHub ยท npm

Top comments (0)