AdfDotNet.Core 1.0.0

dotnet add package AdfDotNet.Core --version 1.0.0
                    
NuGet\Install-Package AdfDotNet.Core -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="AdfDotNet.Core" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="AdfDotNet.Core" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="AdfDotNet.Core" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add AdfDotNet.Core --version 1.0.0
                    
#r "nuget: AdfDotNet.Core, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package AdfDotNet.Core@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=AdfDotNet.Core&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=AdfDotNet.Core&version=1.0.0
                    
Install as a Cake Tool

AdfDotNet

A .NET document model for the Atlassian Document Format (ADF) — the JSON structure Jira and Confluence use for rich text. AdfDotNet gives you a typed ADF model, a fluent builder to compose documents in code, and converters to move between ADF, HTML, Markdown, and JSON.

var document = AdfDocumentBuilder.Build(doc => doc
    .Heading(1, "Hello World")
    .Paragraph(p => p
        .Text("This is ")
        .Text("bold", m => m.Strong())
        .Text(" and this is a ")
        .Text("link", m => m.Link("https://example.com"))
        .Text(".")));

string json = document.ToJson(prettyPrint: true);

Packages

The library is split into a lightweight core plus opt-in satellite packages, so you only pull in what you use. All packages target netstandard2.0 (.NET Framework 4.6.2+, .NET Core 2.0+, .NET 5+).

Package What it's for Depends on
AdfDotNet.Core The ADF document model, validation against the ADF spec, and the fluent builder HtmlAgilityPack
AdfDotNet.Converters.Html HTML ↔ ADF conversion Core, HtmlAgilityPack
AdfDotNet.Converters.Markdown Markdown ↔ ADF conversion Core, Markdig
AdfDotNet.Json.Newtonsoft ADF ↔ JSON serialization Core, Newtonsoft.Json
AdfDotNet Meta-package: references Core + every satellite all of the above

Getting started

Install the AdfDotNet meta-package to get the model, builder, and all three converters:

dotnet add package AdfDotNet

or install just AdfDotNet.Core plus the individual satellite(s) you need:

dotnet add package AdfDotNet.Core
dotnet add package AdfDotNet.Converters.Html

Every satellite works two ways:

  • Static, no DI required — call AdfHtmlConverter/AdfMarkdownConverter/AdfJsonConverter-style static methods directly, good for scripts, console apps, and tests.
  • DI-registered — call services.AddAdfHtmlConverter() / AddAdfMarkdownConverter() / AddAdfNewtonsoftJson() in an ASP.NET Core (or any IServiceCollection-based) app and inject IAdfHtmlConverter / IAdfMarkdownConverter / IAdfDocumentSerializer.

Examples

Build a document with the fluent builder

using AdfDotNet.Builders;

var document = AdfDocumentBuilder.Build(doc => doc
    .Heading(1, "Release notes")
    .Paragraph(p => p.Text("Changes in this release:"))
    .BulletList(list => list
        .Item(i => i.Paragraph(p => p.Text("Fixed a bug")))
        .Item(i => i.Paragraph(p => p.Text("Added a feature"))))
    .Rule());

// Structural legality (e.g. which node types are allowed as children of which) is not enforced while
// building — call Validate() when you want to confirm the tree is well-formed.
var result = document.Validate();
if (!result.IsValid)
{
    foreach (var error in result.Errors)
        Console.WriteLine(error);
}

Serialize to / from ADF JSON

using AdfDotNet.DataConverters; // AdfJsonConverter, and the ToJson() extension method

string json = document.ToJson(prettyPrint: true);
AdfDocument roundTripped = AdfJsonConverter.Deserialize(json);

Convert to and from HTML

using AdfDotNet.FormatConverters;

string html = AdfToHtmlConverter.Convert(document);
AdfDocument fromHtml = HtmlToAdfConverter.Convert("<h1>Hello</h1><p>World</p>");

Convert to and from Markdown

using AdfDotNet.FormatConverters;

string markdown = AdfToMarkdownConverter.Convert(document);
AdfDocument fromMarkdown = MarkdownToAdfConverter.Convert("# Hello\n\nWorld");

Register converters for dependency injection

using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection();
services.AddAdfHtmlConverter();
services.AddAdfMarkdownConverter();
services.AddAdfNewtonsoftJson();

var provider = services.BuildServiceProvider();
var htmlConverter = provider.GetRequiredService<IAdfHtmlConverter>();
string html = htmlConverter.ConvertToHtml(document);

ADF scope and converter limitations

ADF is a format defined by, and tied to, Jira Cloud/Confluence Cloud — some of it (e.g. media's Jira Media Services identifiers) is meaningless outside a Jira Cloud tenant. AdfDotNet's document model and JSON serialization aim for full spec fidelity; the HTML and Markdown converters are convenience helpers for the self-contained subset of ADF, not a complete Jira/Confluence renderer — some node types can't be represented in HTML or Markdown at all, for reasons rooted in the format itself, not converter bugs.

Document model + JSON: full spec coverage

AdfNodeType/AdfMarkType (AdfDotNet.Core/Enums) model every node and mark type on the Jira ADF spec: 32 of 32 node types and 10 of 10 marks. All of them can be built with the fluent builder, are checked by Validate(), and round-trip losslessly through AdfDotNet.Json.Newtonsoft. A test builds one document containing every type and checks all three.

Node types

Category Implemented
Root doc
Top-level block blockquote, bodiedSyncBlock, bulletList, codeBlock, expand, heading, mediaGroup, mediaSingle, multiBodiedExtension, orderedList, panel, paragraph, rule, syncBlock, table
Child block blockTaskItem, extensionFrame, listItem, media, nestedExpand, tableCell, tableHeader, tableRow
Inline date, emoji, hardBreak, inlineCard, mediaInline, mention, status, text

The spec page lists blockTaskItem without saying what contains it. Its only parent in the ADF JSON schema is taskList, so taskList and taskItem (ordinary checkbox lists) are modeled too, although they aren't on the spec page.

Marks

Implemented
backgroundColor, border, code, em, link, strike, strong, subsup, textColor, underline

What happens with a type this library doesn't model during JSON deserialization: some types exist only in the ADF JSON schema (@atlaskit/adf-schema) and not in the spec: decisionList, layoutSection, extension, blockCard, the alignment/indentation/breakout/annotation marks, and so on. They occur in Confluence content, but this library deliberately doesn't model them. They are silently dropped (a node together with its content) and the rest of the document is kept, so such documents still load and what remains is valid for Jira. A type name that isn't ADF at all (a typo, a non-ADF payload, genuinely malformed input) still throws JsonSerializationException, so real mistakes keep failing loudly. See AdfSpecCoverage in AdfDotNet.Core/Models for the registry backing this distinction, and JSON serialization for more detail.

HTML and Markdown converters: inherent, permanent limitations

Every type the spec defines is handled by both converters, but not every type can be expressed in HTML or Markdown. Each one either round-trips, renders one-way as something else (which is what comes back), or is dropped (renders as nothing). Nothing throws. KitchenSinkTests checks every row below by round-tripping one document that contains every node and mark type.

Type HTML Markdown
Paragraphs, headings, lists, blockquotes, code blocks, rules, hard breaks, tables round-trips round-trips (a first table row always comes back as a header row)
strong, em, code, link, strike round-trips round-trips
underline, subsup, textColor, backgroundColor round-trips (<u>, <sub>/<sup>, inline style) dropped, text kept
panel round-trips (<div data-panel-type>) round-trips (> [!NOTE]-style alert)
expand, nestedExpand round-trips (<details>/<summary>) round-trips (raw-HTML <details> block); one-way as a bold title line in a table cell
taskList, taskItem, blockTaskItem round-trips (<ul data-adf-type="taskList">) round-trips as GFM - [ ]/- [x], with new localIds; a one-paragraph blockTaskItem comes back as a taskItem
mention, date, status round-trips (data attributes) one-way, as text
inlineCard one-way, as linked text one-way, as linked text (an autolink)
emoji one-way, as text one-way, as text
bodiedSyncBlock, multiBodiedExtension, extensionFrame one-way, as their body one-way, as their body
media, mediaSingle, mediaGroup, mediaInline, syncBlock, the border mark dropped dropped

Attributes: an ordered list's order and a link's title round-trip through both formats; table and cell attrs (colspan/rowspan, cell background, colwidth, the table's layout/width/...) round-trip through HTML only, since pipe tables can't carry them.

The gaps have two different causes:

  • Lacking context outside a Jira Cloud tenant. media's attrs (id/type/collection) are Jira Cloud Media Services identifiers, not a fetchable URL. There's nothing to render an <img src> or Markdown ![alternate text is missing from this package README image]() from, and no way to derive those identifiers from an incoming <img>/image link either — so the media types are supported by the document model and JSON only. syncBlock and bodiedSyncBlock reference Jira-side resources, and multiBodiedExtension/extensionFrame Jira apps, so a body can be shown but not rebuilt into its wrapper.
  • Target format limitations. Markdown/CommonMark has no syntax for the four decoration marks, mentions, dates or statuses, and GFM pipe tables require the delimiter row immediately after the first row.

Both are covered in more depth in HTML conversion and Markdown conversion.

Unmapped/unrecognized HTML tags and Markdown constructs are already dropped by both converters (not thrown) — this is the existing, established fallback behavior for content the converters can't represent, consistent with how the JSON layer handles schema-only ADF types above.

HTML and Markdown also nest more freely than ADF (a heading or a nested quote inside a blockquote, a quote or table inside a list item, code inside bold text). Both converters reshape their output with AdfNormalizer so its nesting and marks pass Validate(). Illegal headings become paragraphs, illegal wrappers are unwrapped into their content, empty lists and tables are dropped, and code/link win mark clashes. You can run the same pass on any document with document.Normalize(). See Validation.

Documentation

See docs/ for more depth:

Building from source

git clone https://github.com/decimvs/AdfDotNet.git
cd AdfDotNet
dotnet build AdfDotNet.slnx

AdfDotNet.TestingApp is a small console app for manually exercising the API — not an automated test project. Run it with:

dotnet run --project AdfDotNet.TestingApp

The automated test suite lives in AdfDotNet.Tests (xUnit, targets net10.0 and net472):

dotnet test AdfDotNet.slnx

Author

Created and maintained by Guillermo Espert Carrasquer.

License

Copyright (c) 2026 Guillermo Espert Carrasquer.

Licensed under the MIT License — see LICENSE.txt.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (4)

Showing the top 4 NuGet packages that depend on AdfDotNet.Core:

Package Downloads
AdfDotNet.Json.Newtonsoft

Lossless Atlassian Document Format (ADF) JSON serialization for AdfDotNet, built on Newtonsoft.Json. Usable statically or through dependency injection.

AdfDotNet.Converters.Html

HTML to Atlassian Document Format (ADF) conversion and back for AdfDotNet, built on HtmlAgilityPack. Usable statically or through dependency injection.

AdfDotNet.Converters.Markdown

Markdown to Atlassian Document Format (ADF) conversion and back for AdfDotNet, built on Markdig (CommonMark + GFM). Usable statically or through dependency injection.

AdfDotNet

Atlassian Document Format (ADF) for .NET: a typed document model, fluent builder, validation, and ADF conversion to and from HTML, Markdown and JSON. Meta-package that installs AdfDotNet.Core and every converter package.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 254 9/30/2026