# swift-markdown: a cmark-gfm parser and a persistent markup tree for Swift

> Swift Markdown parses, builds and edits Markdown in Swift by wrapping cmark-gfm and exposing an immutable, copy-on-write syntax tree. It is a parsing library, not a renderer, and that distinction decides whether it fits your project.

**swiftlang/swift-markdown** — A Swift package for parsing, building, editing, and analyzing Markdown documents.

- Repository: https://github.com/swiftlang/swift-markdown
- Website: https://swiftpackageindex.com/swiftlang/swift-markdown/documentation/markdown
- Stars: 3,427 · Forks: 301
- Language: Swift
- License: Apache-2.0
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/swiftlang-swift-markdown

## What swift-markdown is for, and who should reach for it

Markdown in a Swift app usually means one of two jobs. The first is turning text into something a user can look at. The second is understanding the text: finding headings, rewriting links, checking structure, or converting to another format. Swift Markdown takes the second job. The README describes it as a Swift package for "parsing, building, editing, and analyzing Markdown documents," and the API it shows is a syntax tree, not a view.

That makes it a fit for tool authors. A documentation generator that needs to collect headings, a migration script that rewrites relative links, a linter that enforces a heading hierarchy, or an editor that manipulates a document without re-serializing it from scratch all sit in the target audience. If your goal is a text view with bold and italic runs on screen, this package is upstream of that work, not a replacement for it.

The parser is powered by GitHub-flavored Markdown's cmark-gfm implementation, so the dialect follows that spec closely. The README adds a caveat worth reading twice: "As the needs of the community change, the effective dialect implemented by this library may change." Dialect drift is a real risk for anyone who stores parsed structure or diffs it across versions.

## The markup tree: immutable, thread-safe, copy-on-write

The design choice that shapes everything else is the tree itself. The README states that the markup tree is made of "immutable/persistent, thread-safe, copy-on-write value types that only copy substructure that has changed," and points at SwiftSyntax as another example of the same strategy.

Concretely, that means a parse produces a value you can pass across threads without locking, and an edit produces a new tree that shares most of its nodes with the old one. For an editor that keeps an undo stack, or a server that parses the same document for several consumers, sharing unchanged substructure keeps memory from growing with every revision. The trade-off is that mutation is not in-place. Code that wants to change a node and keep the old document around has to hold both values, and code that wants to mutate deeply has to thread new trees back out through the call chain.

The package's parsing entry point is a single initializer, Document(parsing:), which accepts either a String or a URL. That URL overload matters for tools that walk a directory of Markdown files, since the parser can read from disk without an intermediate String. The repository also contains a Snippets directory and a Tools directory alongside Sources and Tests, which suggests the project ships its own example and tooling code, though the README does not describe what is in either.

## Adding swift-markdown to Package.swift and parsing a first document

Installation is through Swift Package Manager. The README gives the dependency as a branch reference to main, which is what you would use to track the repository directly:

```swift
.package(url: "https://github.com/swiftlang/swift-markdown.git", branch: "main"),
```

Then declare the Markdown product on any target that needs it. The product name is Markdown and the package name is swift-markdown, and both strings have to match exactly or the build will not resolve the dependency:

```swift
.target(
    name: "MyTarget",
    dependencies: [
        .product(name: "Markdown", package: "swift-markdown"),
    ]
),
```

With that in place, parse a string and print the tree. The README's example uses Document(parsing:) and debugDescription(), and the output it shows is an indented tree of node types with the text leaves quoted:

```swift
import Markdown

let source = "This is a markup *document*."
let document = Document(parsing: source)
print(document.debugDescription())
// Document
// └─ Paragraph
//    ├─ Text "This is a markup "
//    ├─ Emphasis
//    │  └─ Text "document"
//    └─ Text "."
```

If you see that tree, the parse worked. Note the shape: the asterisks are gone and an Emphasis node wraps a Text node, so the tree reflects structure rather than source characters. For anything beyond this example, the README defers to the documentation site at swiftlang.github.io/swift-markdown, which is where the visitor and walker APIs live.

## It parses Markdown. It does not render it

The most common wrong turn with this package is expecting output. There is no view, no attributed string, and no HTML emitter in the README. The example prints a debug description of the tree. Everything between the tree and a screen or an HTML file is work you supply.

That is a deliberate boundary, and it is the reason the related searches for Swift markdown UI, iOS Markdown renderer and Swift markdown to attributedstring do not describe this library. If you need attributed output on Apple platforms, the tree is a good intermediate representation, but you will write the conversion, and you will decide how to map every node type, including the ones cmark-gfm produces that have no obvious visual equivalent.

A second limitation is dialect. Because the parser tracks cmark-gfm, constructs outside that spec are not guaranteed to parse the way another Markdown implementation would. Documents written against a different flavor can produce a tree that is structurally correct for cmark-gfm and wrong for your intent. The README's own note that the effective dialect may change over time means a pinned dependency is the safer default than a branch reference for anything long-lived.

## swift-markdown compared with a Markdown-to-HTML converter

The obvious alternative approach is a converter: hand it Markdown, get HTML back, and let a web view handle display. That is a shorter path to pixels and it is the right choice when the document is a means to an end and nobody needs to inspect it.

The difference is where the work happens. A converter collapses the document into a string and throws away structure, so any question about the document (which headings exist, which links are relative, whether the heading levels skip) has to be answered by re-parsing or by pattern matching on text. Swift Markdown keeps the structure as the primary artifact and makes serialization the derived step. If your pipeline needs to both inspect and emit, the tree pays for itself. If it only needs to emit, the tree is an extra layer.

There is a middle case worth naming: projects that already depend on cmark-gfm through another binding. Swift Markdown is a Swift-native wrapper over the same parser, so the parse results should be familiar, but the value-add is the Swift tree and its value semantics rather than the parsing engine itself.

## Maintenance, releases and the Apache-2.0 licence

The repository is not archived, and the last push was on 2026-09-22, one day before the most recent release. Releases are not frequent: 0.9.0 on 2026-09-21, 0.8.0 on 2026-05-07, and 0.7.3 on 2025-10-09. That cadence is consistent with a library that has settled, but it also means a fix you need may wait months, and the version numbers are still in the 0.x range, where the README's warning about a changing dialect carries more weight.

Upgrade cost is mostly API surface. Because the tree is value-typed and the entry point is an initializer, most upgrades are compile-time events rather than runtime surprises, which is the better failure mode. The branch reference in the README's install snippet is the one thing to change for production use: pinning to a released tag gives you a known tree shape, while tracking main means your build can move under you.

The licence is Apache-2.0, and the repository carries both LICENSE.txt and NOTICE.txt at the top level. Apache-2.0 includes an explicit patent grant and requires that notices be preserved, so redistributing a binary that embeds this package means carrying the NOTICE file with it. That is a description of the licence terms, not legal advice; check with counsel for your distribution model.

## Conclusion

Adopt swift-markdown when you need a parsed, inspectable Markdown tree in Swift: linters, documentation pipelines, converters, or editors that rewrite source. Do not adopt it expecting rendered output, since the package exposes no renderer and the related searches for Swift markdown UI and iOS Markdown renderer point at a job it does not do. Before committing, verify that the cmark-gfm dialect matches the Markdown your users actually write, and check the documentation site for the current API surface, because the README only shows Document(parsing:) and debugDescription().

## FAQ

### Can I use swift-markdown in SwiftUI?

The package produces a markup tree, not a view, so SwiftUI integration means walking the tree and building your own views from it. The README shows only Document(parsing:) and debugDescription(), and the documentation site is where the visitor APIs are described.

### What is the purpose of swift-markdown?

It is a Swift package for parsing, building, editing, and analyzing Markdown documents, powered by cmark-gfm. Its output is an immutable, thread-safe, copy-on-write markup tree rather than rendered text.

### How do I install swift-markdown?

Add the package to your Package.swift dependencies and declare the Markdown product on the target that needs it. The README's snippet points the dependency at the main branch, so pin to a release tag if you want a fixed tree shape.

### Does swift-markdown render Markdown to HTML or to an attributed string?

No. The README documents parsing into a tree and printing a debug description; it shows no HTML emitter and no attributed string output. Conversion from the tree to a display format is work the caller supplies.

### Which Markdown dialect does swift-markdown implement?

The parser is powered by GitHub-flavored Markdown's cmark-gfm implementation, so it follows that spec closely. The README notes that the effective dialect may change as community needs change.

## Sources

- [License: Apache-2.0](https://github.com/swiftlang/swift-markdown/blob/main/LICENSE)
- [Project website](https://swiftpackageindex.com/swiftlang/swift-markdown/documentation/markdown)
- [README](https://github.com/swiftlang/swift-markdown/blob/main/README.md)
- [Releases](https://github.com/swiftlang/swift-markdown/releases)
- [swiftlang/swift-markdown on GitHub](https://github.com/swiftlang/swift-markdown)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/swiftlang-swift-markdown
