league/commonmark follows SemVer for the code, not for the HTML
Highly-extensible PHP Markdown parser which fully supports the CommonMark and GFM specs.
At a glance
- What is it?
- A PHP CommonMark and GFM parser with three changelogs, three static analysers and a backward-compatibility gate. Its own policy paragraph is the one to read first, because it says patch releases may still change the AST and the rendered HTML, and because the two security options the page demonstrates are never given a default.
- Who is it for?
- Adopt league/commonmark if you need a spec-complete CommonMark parser with a documented extension mechanism and a real backward-compatibility gate, which the roave-bc-check configuration in the repository gives you.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 13, 2026, and from our analysis. They are not legal advice.
Star history
- thephpleague/commonmark2,980 ★ · +2
| Repository | 2026-09-24 | 2026-11-13 |
|---|---|---|
| thephpleague/commonmark | 3k | 3k |
2 Stars in the last 50 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
SemVer covers the codebase, not the rendered output
The Versioning section is two paragraphs and the first one is the one to quote.
SemVer is followed closely. Minor and patch releases should not introduce breaking changes to the codebase, however they might change the resulting AST or HTML output of parsed Markdown, due to bug fixes or spec changes. As a result, you might get slightly different HTML, but any custom code built onto this library should still function correctly.
So the guarantee is about API stability, not about output stability. A test suite that asserts on rendered HTML can fail on a patch release for a reason the release notes would describe as a fix, and the README pre-empts that rather than hiding it.
The second paragraph draws the other boundary. Any class or method marked `@internal` is not intended for use outside the library and is subject to breaking changes at any time, so please avoid using them. That is a normal annotation, and here it is load-bearing: it is the escape hatch that makes the first paragraph's promise possible.
Put together, the policy is coherent. The public API is stable, the output is not, and anything marked internal is fair game. What it does not offer is a way to pin the output, which is a decision the adopter has to make with their own tests.
The two security options are set twice and never given a default
Both converter examples in the README pass the same pair of options:
use League\CommonMark\CommonMarkConverter;
$converter = new CommonMarkConverter([
'html_input' => 'strip',
'allow_unsafe_links' => false,
]);
echo $converter->convert('# Hello World!');That is `html_input` set to `strip` and `allow_unsafe_links` set to `false`, passed explicitly, and the CAUTION block underneath says that if you will be parsing untrusted input from users you should consider setting those two options per the example above, with a link to the library's security documentation. If you also choose to allow raw HTML from untrusted users, it suggests a library such as HTML Purifier for additional filtering.
Two things are missing from that sequence. The page never says what the defaults are, so a reader who writes `new CommonMarkConverter()` with no options gets behaviour they have to look up elsewhere. And the GFM converter's own list of additions includes an item called Disallowed raw HTML, alongside autolinks, strikethrough, tables and task lists, which means the two converters differ in their safe default as well as in their feature set.
The encoding note is the third constraint, and it is stated in a single sentence: only UTF-8 and ASCII encodings are supported, and anything else should be converted to UTF-8 before it goes through the library.
The support window is three months, or nine after a major release
The Maintenance & Support section states two windows.
When a new minor version is released, the previous one continues to receive security and critical bug fixes for at least three months. When a new major version is released, the previous one receives critical bug fixes for at least three months and security updates for six months after that new release comes out.
So a minor bump buys you a quarter of fixes for the version you are on, and a major bump buys you a quarter of critical fixes plus half a year of security updates. Then nothing is promised.
The section closes with a parenthesis that is easy to skim past: the policy may change in the future and exceptions may be made on a case-by-case basis. Both halves of that are load-bearing, and neither is unusual to see in a small-project policy, but they do mean the three-month figure is a floor rather than a commitment.
The paid path runs through Tidelift, which sells a subscription including notification of new releases and security updates. The same company is the security contact: to report a vulnerability you go through the Tidelift security contact, and Tidelift coordinates the fix and the disclosure.
So for a library that other people's applications parse user input with, the disclosure path for a security problem runs through a commercial service rather than a security.txt in the repository.
The integration list is a decade behind the PHP requirement
The Related Packages section is where a careful reader notices the passage of time.
The Integrations list contains CakePHP 3, Drupal, Laravel 4+, Sculpin, Symfony 2 and 3, Symfony 4, a Twig Markdown extension, a Twig filter and tag, and a Laravel CommonMark Blog.
Read against the package's own requirement of PHP 7.4 or higher, that is a list from the previous decade. There is no official Laravel 5 or later entry; the one Laravel item points at a third-party repository and is labelled Laravel 4+. Symfony appears twice, split by major version. Twig appears twice, split by integration style. And one of the nine entries is a blog rather than a package.
None of that means the integrations are broken, and several of those frameworks are still widely used. It means the list is a historical record rather than a compatibility matrix, so a reader choosing a framework integration should assume they are reading an archived suggestion rather than a maintained path.
The other two lists are healthier. Included Extensions points at the library's own extension documentation for the full set bundled with the package, and seven community extensions are named with one-line descriptions each, covering emoji, superscript and subscript, YouTube iframes, lazy image loading, marked text, Pygments highlighting and LaTeX rendering. Anyone building their own is invited to submit a pull request to add it to that list, and Packagist's `commonmark-extension` package type is offered as the place to find the rest.
Three analysers, two hosted services, and one read-only mount
The repository root carries more analysis configuration than source-adjacent configuration, and each tool answers a different question.
`phpcs.xml.dist` is a coding-standards configuration. `phpstan.neon.dist` and `psalm.xml` are two separate static analysers, which is unusual to run both. `.styleci.yml` and `.scrutinizer.yml` point at two hosted services. `.editorconfig` handles whitespace, and `.phpstorm.meta.php` carries IDE metadata.
The interesting one is `roave-bc-check.yaml`. Backward compatibility checking is what makes the SemVer promise enforceable rather than aspirational, and the repository wires it into a two-service compose file:
bc-check:
image: nyholm/roave-bc-check
volumes:
- .:/app:roThe second service mounts the working copy read-only, which is the mechanism that stops a compatibility check from writing to the tree it is inspecting. The first service builds from `./docker` and mounts the same directory read-write for the PHP work.
One detail in that file is dated. The compose file declares `version: '3.7'`, a key that current Compose versions ignore and warn about, since the specification version stopped being meaningful after Compose v2.
Also present: `CLAUDE.md`, `CHANGELOG.md` beside `CHANGELOG-1.x.md` and `CHANGELOG-0.x.md`, a `RELEASE_CHECKLIST.md`, a `.nojekyll` file so the `docs/` tree renders on GitHub Pages, and a `commonmark-banner.png` at the root.
The default branch is 2.10 and the build badge watches main
The branch arrangement is worth knowing before you file anything.
The repository's default branch is `2.10`, a maintenance branch named after the minor version rather than `main`. The three visible releases sit on it: 2.10.1 on 2026-09-07, then 2.10.2 and 2.10.3 on 2026-09-21, the last two about forty minutes apart.
That last detail is the kind of thing worth pausing on. Two patch releases of the same minor version, published the same afternoon, usually means one of them was a partial or broken release that needed replacing. Nothing on the page says which.
The Tests badge in the README header queries the workflow with `branch:main`, so the visible build status is for a branch that is not the default one. If you are judging whether the current tip is green, the badge will not tell you; you have to look at the Actions run for `2.10`.
So the branch layout gives maintenance work a home per minor version, and the changelog files follow the same convention with one file per major line. What the badge does not follow is the convention.
A drop-in replacement that is also a set of separable extensions
The GitHub-Flavored Markdown section describes a class rather than a dialect, and the description contains a small tension worth resolving before you rely on it.
`GithubFlavoredMarkdownConverter` is a drop-in replacement for `CommonMarkConverter` which adds the additional features found in the GFM spec: autolinks, disallowed raw HTML, strikethrough, tables and task lists. Then it points at the extensions documentation for how to include only certain GFM features if you do not want them all.
So both are true. The class is a drop-in swap at the call site, and the five features behind it are individually selectable through the extension mechanism. The second sentence is the more useful one for a production system, because a table implementation or an autolink resolver is a behaviour choice rather than a formatting preference.
That extension mechanism is the substantive design decision in the library, and the README treats it as the point. Custom parsers and renderers are bundled into extensions that extend CommonMark, the library ships a set of them itself, and the community list is published on the same page with an invitation to add to it.
The distinction the page is drawing is between the core parser, which follows the spec, and the extension layer, which is where everything beyond the spec lives. A reader who wants spec-exact output and nothing else is looking at `CommonMarkConverter`; a reader who wants tables is configuring an extension, whether they do that through the GFM class or by assembling their own.
Editorial conclusion
Adopt league/commonmark if you need a spec-complete CommonMark parser with a documented extension mechanism and a real backward-compatibility gate, which the roave-bc-check configuration in the repository gives you. Do not adopt it if you snapshot rendered HTML and treat that output as stable, because the README states plainly that minor and patch releases may change the resulting AST and HTML, and because the previous minor receives security and critical fixes for only three months. Verify first what the defaults for html_input and allow_unsafe_links are in the version you install, since the page demonstrates both set to the safe values twice and never states the defaults, and decide whether you need the GFM converter at all, given that one of its five added features is disallowing raw HTML.
Frequently asked questions
what is league commonmark
It is a highly-extensible PHP Markdown parser created by Colin O'Dell that supports the full CommonMark spec and GitHub-Flavored Markdown, and is based on the CommonMark JS reference implementation by John MacFarlane. It requires PHP 7.4 or higher with the mbstring extension, and only UTF-8 and ASCII input encodings are supported.
commonmark vs gfm
In this library the difference is which converter class you construct. `GithubFlavoredMarkdownConverter` is described as a drop-in replacement for `CommonMarkConverter` that adds autolinks, disallowed raw HTML, strikethrough, tables and task lists. The extensions documentation also explains how to include only certain GFM features rather than all of them.
How do I safely render user-supplied Markdown with league/commonmark?
The caution block says to set `html_input` and `allow_unsafe_links` if you are parsing untrusted input, and links to the library's security documentation. Its examples set both explicitly, `html_input` to `strip` and `allow_unsafe_links` to `false`, but the page does not state what the defaults are. If you do allow raw HTML from untrusted users, it suggests a library such as HTML Purifier for further filtering.
How long does league/commonmark support previous versions?
When a new minor version is released the previous one receives security and critical bug fixes for at least three months. When a new major version is released the previous one receives critical bug fixes for at least three months and security updates for six months after that. The README notes the policy may change and that exceptions may be made case by case.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/thephpleague-commonmark)Star history chart for your README
[](https://hysenlabs.com/projects/thephpleague-commonmark)Was this useful?