ekumanov / flarum-ext-markdown-tables
Markdown tables for Flarum 2.0, with Tiptap rich-text editor integration.
Package info
github.com/ekumanov/flarum-ext-markdown-tables
Language:JavaScript
Type:flarum-extension
pkg:composer/ekumanov/flarum-ext-markdown-tables
Requires
- php: ^8.2
- flarum/core: ^2.0@beta
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Adds proper markdown table support to Flarum 2.0, both in rendered posts and inside the FriendsOfFlarum Rich Text WYSIWYG editor.
This is a Flarum 2.0 port of askvortsov/flarum-markdown-tables — the original Flarum 1.x extension was built on askvortsov-rich-text (ProseMirror-based), which has been replaced in 2.0 by fof-rich-text (Tiptap v3-based). The port was made with Claude Code.
Screenshots
| Light | Dark |
|---|---|
![]() |
![]() |
Tables size to their natural content, so a typical post table fits comfortably on mobile. Tables wider than the post column get a horizontal-scroll wrapper instead of forcing every cell to crush.
What you get
Type a standard pipe table in the composer:
| Name | Role | Notes |
|-------|:------:|----------------:|
| Alice | Admin | Founding member |
| Bob | Editor | Joined 2024 |
| Carol | Reader | Lurker mode |
…and your post renders it as a proper, styled HTML table — with header row, column alignment from the :---: markers, alternating row backgrounds, and horizontal scrolling when the table is wider than the post column.
Features
- Pipe-table parsing — standard GitHub-flavoured Markdown syntax. Handles left/center/right column alignment via
:---,:---:, and---:separators. - Rich-text editor integration — when
fof/rich-textis enabled, an "Insert table" button appears in the composer toolbar. Set columns and rows, click insert, type into cells. - Markdown round-trip — tables created in the WYSIWYG editor serialize back to clean pipe-table markdown, so the post stays human-readable in the database and editable in plain mode.
- Responsive overflow — wide tables get wrapped in a horizontally-scrolling container so they never blow out narrow viewports.
- Theme-aware — borders, header background, and zebra striping use Flarum's CSS custom properties, so tables follow your light/dark scheme automatically.
- No configuration — install, enable, done.
- Works without
fof/rich-texttoo — if you don't use the WYSIWYG editor, posts still parse and render markdown tables correctly.
Requirements
- Flarum 2.0 (tested against 2.0.0-rc.1)
- PHP 8.2+
- Optional:
fof/rich-text2.0+ for the WYSIWYG insert button. Markdown rendering works without it.
Installation
composer require ekumanov/flarum-ext-markdown-tables
rm -rf storage/formatter/*
php flarum cache:clear
Then enable the extension in the admin panel under Extensions > Markdown Tables.
Why
rm -rf storage/formatter/*? Flarum compiles the s9e/text-formatter renderer into a PHP class understorage/formatter/Renderer_*.php.php flarum cache:cleardoes not touch that file. If a renderer was compiled before this extension was active, it has no template branches for<TABLE>/<TR>/<TD>etc., and existing posts that contain table XML will silently render as empty space. Removing the file forces a regenerate that includes the table templates.
Upgrading from Flarum 1.x with askvortsov/flarum-markdown-tables
If you're coming from Flarum 1.x and previously had askvortsov/flarum-markdown-tables installed, your existing post XML is already compatible — both extensions enable the same s9e/text-formatter PipeTables plugin and the stored XML uses the same <TABLE> tag names. No re-parse of old posts is needed.
But you almost certainly do need to clear the formatter cache, because during the period between the 1.x→2.0 migration and the install of this extension the renderer was recompiled without table templates. Run:
composer require ekumanov/flarum-ext-markdown-tables
rm -rf storage/formatter/*
php flarum cache:clear
php flarum assets:publish
After that, every existing post that already had a markdown table will render as a real <table> again — no edits required.
Updating
composer update ekumanov/flarum-ext-markdown-tables php flarum cache:clear
How it works
Backend
The PHP side is small. We register an Extend\Formatter callback that enables the s9e/text-formatter PipeTables plugin:
(new Extend\Formatter()) ->configure(function (Configurator $config) { $config->PipeTables; });
PipeTables is already shipped inside s9e/text-formatter (a Flarum core dependency), so this adds zero new code paths to the parser — it just turns on a feature that's already there.
Frontend (forum, always-on)
Two small things happen on the forum side regardless of which editor you use:
-
Wrap rendered tables for horizontal scroll. PipeTables emits bare
<table>elements; without a wrapper, a table wider than the post column overflows or breaks layout. We hook intoPost#oncreate/onupdateand wrap each<table>in a<div class="markdown-table-wrapper">withoverflow-x: auto. The wrap is idempotent — adata-markdown-tables-wrappedattribute prevents double-wrapping when Mithril rerenders. -
Style the table. Borders use
var(--control-bg), header rows pick upvar(--control-bg)as background, even rows get a subtle striping. Everything goes through Flarum 2's CSS custom properties so it follows the theme automatically.
Frontend (composer, when fof/rich-text is enabled)
This is where most of the code lives, because Tiptap doesn't know about pipe tables out of the box. The integration happens in four pieces:
1. Tiptap node specs
fof/rich-text exposes Node from @tiptap/core via flarum.reg.get('fof-rich-text', 'common/tiptap/tiptap'). We use that to define six nodes:
table > tableHead > tableRow > tableHeader (one head row)
> tableBody > tableRow > tableCell (zero or more body rows)
Each node has standard parseHTML / renderHTML rules. Cell nodes carry an optional style attribute that holds the alignment (text-align: left|center|right), parsed from the markdown separator and replayed when the cell renders.
The Node constructor is captured lazily, after fof/rich-text's async chunk loads — resolving it at module load time would crash, because the chunk hasn't loaded yet. We push our setup work onto TextEditor.prototype.oninit's _loaders array, which Tiptap's editor awaits before construction.
2. Markdown parser tokens
fof/rich-text's MarkdownParserBuilder uses markdown-it in CommonMark mode, which has the table rule disabled. We patch its prototype to:
- Call
.enable('table')on the tokenizer. - Add token-to-node mappings for
table,thead,tbody,tr,th,td. Theth/tdmappings lift thestyleattribute from the markdown-it token onto the node attributes — that's how alignment survives parsing.
3. Markdown serializer
When the user types a table in the WYSIWYG editor and hits submit, Tiptap calls our serializer to turn the doc back into markdown. We hook MarkdownSerializerBuilder.prototype.buildNodes and add a table handler that walks tableHead → tableRow → tableHeader, emits the header pipe row, then the alignment separator row (| --- | :---: | ---: |), then each tableBody row.
Pipe characters that appear inside a cell are escaped to \| so they don't break the table syntax.
4. Toolbar dropdown
InsertTableDropdown.js is a Mithril component that extends Flarum's Dropdown. Its menu has two faces:
- Outside a table — a small form with "columns" and "rows (including header)" inputs and an "Insert table" button. Clicking it dispatches our
insertTable(rows, cols)Tiptap command, which builds a freshtablenode populated with single-space cells (PipeTables drops empty cells, so the space keeps the structure intact through round-trips). - Inside a table — a context menu with "Insert/delete row before/after", "Insert/delete column before/after", and "Delete table".
The button is added to the toolbar via extend(TiptapMenu.prototype, 'items', …), so it sits alongside Bold, Italic, Image, etc.
A note on bundle isolation
Our extension does not bundle @tiptap/extension-table. Instead we built minimal Node specs ourselves and reused fof/rich-text's exposed @tiptap/core Node constructor. This avoids shipping a duplicate copy of Tiptap-core or prosemirror-tables — nodes share the same Tiptap instance the editor is using, so instanceof checks and schema lookups all line up.
The trade-off: we don't get column-resize handles or the cell-selection plugin you'd get from @tiptap/extension-table. For a markdown forum, that's the right trade — pipe tables can't express column widths anyway, so column-resize would only set state that disappears on save.
Limitations
- Single-column tables don't render. This is a PipeTables limitation, not ours — GitHub-flavoured Markdown's pipe-table grammar requires at least two columns.
- Cells contain inline content only. No nested block elements (lists, quotes, code blocks) inside cells. This matches the standard pipe-table specification.
Links
- Packagist
- GitHub
- Original Flarum 1.x extension by Alexander Skvortsov
License
MIT — see LICENSE. Original copyright notice retained for the Flarum 1.x extension this is ported from.

