A goldmark renderer that outputs Atlassian Document Format (ADF) JSON instead of HTML.
ADF is the native document format used by Atlassian products like Jira Cloud and Confluence Cloud.
- Go 1.27+
go get github.com/ajbeck/goldmark-adf/v2@v2.0.0package main
import (
"bytes"
"fmt"
"log"
"github.com/ajbeck/goldmark-adf/v2"
)
func main() {
// Using convenience function
output, err := adf.Convert([]byte("# Hello World"))
if err != nil {
log.Fatal(err)
}
fmt.Println(string(output))
// Using reusable instance
md := adf.New()
var buf bytes.Buffer
if err := md.Convert([]byte("**Bold** text"), &buf); err != nil {
log.Fatal(err)
}
fmt.Println(buf.String())
}// Enable tables, strikethrough, autolinks, and task lists
md := adf.NewWithGFM()
markdown := []byte(`| Name | Age |
| ---- | --- |
| Alice | 30 |
This has ~~strikethrough~~ text.`)
var buf bytes.Buffer
md.Convert(markdown, &buf)By default, images are converted to linked text. To render images as actual media in Atlassian products, enable external media:
md := adf.New(adf.WithExternalMedia(true))
markdown := []byte(`Check out this diagram:

Pretty cool, right?`)
var buf bytes.Buffer
md.Convert(markdown, &buf)This produces mediaSingle nodes that display images inline in Jira and Confluence.
You can also control image layout:
// ImageLayoutCenter is the default. Other supported values are
// ImageLayoutWide, ImageLayoutFullWidth, ImageLayoutWrapLeft,
// ImageLayoutWrapRight, ImageLayoutAlignStart, and ImageLayoutAlignEnd.
md := adf.New(
adf.WithExternalMedia(true),
adf.WithImageLayout(adf.ImageLayoutWide),
)# Build
go build ./...
# Test
go test ./...This library is designed to work with adf-to-markdown for lossless ADF round-tripping:
ADF JSON --> adf-to-markdown --> Markdown --> goldmark-adf --> ADF JSON
adf-to-markdown converts ADF JSON to Markdown using custom syntax extensions for ADF-specific nodes (status badges, mentions, panels, etc.). goldmark-adf parses that Markdown — including the custom syntax — back into ADF JSON.
Use NewWithGFM() for round-trip workflows. It enables GFM parsing (tables, strikethrough, task lists) and registers the custom extension parsers:
md := adf.NewWithGFM(
adf.WithExternalMedia(true), //  -> mediaSingle
)
var buf bytes.Buffer
if err := md.Convert(markdown, &buf); err != nil {
log.Fatal(err)
}
// buf contains ADF JSONThese extensions are parsed by NewWithGFM() and correspond to the output of adf-to-markdown:
| Markdown Syntax | ADF Node |
|---|---|
[status:text|color] |
status |
@[name](id) |
mention |
[date:1234567890] |
date |
{{placeholder text}} |
placeholder |
[card:url] |
inlineCard / blockCard |
[embed:url] |
embedCard |
:shortcode: |
emoji |
> [!WARNING] |
panel (GitHub alert syntax) |
- [!] text / - [?] text |
decisionList / decisionItem |
- [x] / - [ ] |
taskList / taskItem |
When [card:URL] is the sole unmarked content of a paragraph it emits a
blockCard; elsewhere it emits an inlineCard. [embed:URL] is emitted as a
centered embedCard only in that standalone position, because ADF does not
permit inline embed cards. In mixed inline content it remains literal text.
For the full syntax specification and escaping rules, see the roundtrip-extensions.md document in adf-to-markdown.
- Headings (1-6)
- Paragraphs
- Blockquotes
- Panels (GitHub alert syntax:
> [!NOTE],> [!WARNING], etc.) - Code blocks (fenced and indented)
- Unordered lists
- Ordered lists
- Decision lists (
- [!]/- [?]) - Horizontal rules
- Bold (
**text**) - Italic (
*text*) - Inline code (
`code`) - Links (
[text](url)) - Images (converted to links by default, or external media with
WithExternalMedia(true)) - Hard breaks
- Status badges (
[status:text|color]) - Mentions (
@[name](id)) - Dates (
[date:timestamp]) - Placeholders (
{{text}}) - Cards (
[card:url],[embed:url]) - Emoji (
:shortcode:)
- Tables
- Strikethrough (
~~text~~) - Autolinks
- Task lists (
- [x]/- [ ]astaskList/taskItem)
The adfschema subpackage provides validation against the official Atlassian ADF JSON Schema:
import "github.com/ajbeck/goldmark-adf/v2/adfschema"
if err := adfschema.Validate(jsonBytes); err != nil {
log.Printf("Invalid ADF: %v", err)
}Input:
# Hello World
This is **bold** text with a [link](https://example.com).Output:
{
"version": 1,
"type": "doc",
"content": [
{
"type": "heading",
"attrs": { "level": 1 },
"content": [
{ "type": "text", "text": "Hello World" }
]
},
{
"type": "paragraph",
"content": [
{ "type": "text", "text": "This is " },
{
"type": "text",
"marks": [{ "type": "strong" }],
"text": "bold"
},
{ "type": "text", "text": " text with a " },
{
"type": "text",
"marks": [{ "type": "link", "attrs": { "href": "https://example.com" } }],
"text": "link"
},
{ "type": "text", "text": "." }
]
}
]
}Input (with WithExternalMedia(true)):
Check this out:
Output:
{
"version": 1,
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{ "type": "text", "text": "Check this out:" }
]
},
{
"type": "mediaSingle",
"attrs": { "layout": "center" },
"content": [
{
"type": "media",
"attrs": {
"type": "external",
"url": "https://example.com/diagram.png",
"alt": "Diagram"
}
}
]
}
]
}- Markdown Extensions
- Migrating to v2
- Goldmark to ADF Node Mapping
- Implementation Plan
- HTML Renderer Patterns
- Atlassian Image Handling Research
- Round-Trip Extension Syntax (in adf-to-markdown)
- Library Integration Guide (in adf-to-markdown)
MIT