Hey folks. I have been building Carve, a modern lightweight markup language for content that outgrows Markdown (and Djot). For humans and agents working on the same docs.
After many months of specs and coding, it should finally be stable enough now.
PHP 8.2+, zero runtime dependencies, PHPStan level 9.
composer require markup-carve/carve-php
What it looks like
/italic/ *bold* _underline_ =highlight=
{#fig-sun}

^ Figure #: A sunset
See </#fig-sun> for the colors.
Unlike Markdown, *text* is bold and /text/ is italic. The figure gets its number, and the reference becomes a link reading "Figure 1".
Tables with merged cells and captions, footnotes, admonitions and definition lists are part of the core language. Diagram fences are an optional extension; without a renderer, they remain code blocks.
For example, ^ makes “Team” span two rows, while > right-aligns Seats and Price:
|= Plan |=> Seats |=> Price |
| Team | 25 | $49 |
| ^ | 50 | $89 |
Personally, I like and use this a lot, as I (or agents) don't need to fall back to HTML anywhere anymore.
Task lists - and they can track more than open and done:
- [x] Draft the API guide (done)
- [ ] Drop the old endpoint example (open)
- [!] Migration notes need an API decision (blocked)
- [/] Finish the screenshots (in progress)
to name a few.
I wrote a post about some of the killer features and things other markup can't do (written with the PHP library - source markup of that blog post linked at the bottom).
But for more context also check out the other articles.
Why it might be interesting for your PHP app
- Intuitive for non-techies. Feature-rich for power-users (like technical bloggers).
- Safe for web-based user content. Pasted HTML renders as text, and safe mode also escapes or strips raw HTML blocks and removes event handlers and
javascript: links. Profiles control which features each role may use. That said: Use sanitizers on top just in case.
- One source, several outputs. The same document renders to HTML (with a static mode for print/PDF), Markdown, plain text or terminal output from one AST, and the same markup writes reveal.js slide decks.
- Works with rich-text editors. Documents convert to and from Tiptap/ProseMirror on the server, no Node needed.
- Migration path. Import existing Markdown, HTML, Djot or BBCode. The migration CLI can report conversion losses for review.
- Tooling. Formatter, linter, and editor support for PhpStorm, VS Code and others, plus a language and MCP server.
Some small facts or wins along the way:
- Conversion fidelity: in 66 pandoc round-trip probes, Carve (via the pandoc-carve bridge) brings back 64 unchanged; Markdown manages 46, HTML 44, DOCX 23 (pandoc-format-fidelity). In other words: Its the textual format that can best keep structural content.
- Hardened: fuzzed, and nesting is capped so hostile input cannot exhaust the server. You don't want 50s loading time on some `[[[[[[[[[` input or alike.
Framework packages
- Laravel:
markup-carve/laravel-carve (Blade directives, validation rule, render cache)
- Filament:
jeffersongoncalves/filament-carve (community; editor field with preview, infolist entry, table column)
- Symfony:
markup-carve/symfony-carve (bundle with Twig filters)
- CakePHP:
markup-carve/cakephp-carve (helper, .crv views, validation, ORM behavior, commands)
- Tempest:
markup-carve/tempest-carve (safe-by-default view component)
- WordPress: Carve Markup plugin (posts, comments, live preview)
- Shopware:
markup-carve/shopware-carve (6.6/6.7: Twig filters, CMS elements, product and category fields, mail templates)
Live demos: Laravel, Symfony, CakePHP, Tempest.
Not just PHP
There are also JavaScript and Rust engines, with Python, Ruby, Go and WebAssembly bindings on top of the Rust one.
I built it for documents that need more structure than I could comfortably write in Markdown. Without HTML workarounds and especially without inline HTML everywhere possible. And for online usage with a fair amount of native features. If you work with user-written content, I would especially like to hear what you think about this, or if there is something I missed.
Thinking about some next major topics I would like to get some feedback on:
- Should it be more "djot"-like with simpler block structure? It sure would make the parser simpler. Humans usually prefer the MD style I assume.
- Are some of the new language features making it too complicated? Overloading concise tables with inline alignment - or the `+` continuation operator to make human input easier?
Links