Feature request: Let native plugins register Portable Text inline marks
Feature request: Let native plugins register Portable Text inline marks
Problem
EmDash plugins can contribute Portable Text blocks, but not inline decorators or annotations. The stock editor has a fixed TipTap mark set and fixed Portable Text ↔ ProseMirror conversion. A site can render a custom mark, but it cannot make that mark editable in the Admin or visual inline editor without patching EmDash. Since #2493, an unsupported mark correctly blocks unsafe editing instead of being silently removed; the missing piece is a way to register a supported custom mark.
This is the feature half of #2430 and a concrete follow-up to Discussion #2998. Discussion #1996 also asks for an editor extension point. PR #143 explored a CSS-class-specific approach but was closed as outdated; a general mark seam would serve that use case as well as value-bearing annotations.
Use case
An editor selects part of a paragraph or heading and chooses an arbitrary text color. The content is stored as a Portable Text annotation, not as a block or raw HTML:
{
"_type": "block",
"children": [
{ "_type": "span", "text": "Colored text", "marks": ["color-1"] }
],
"markDefs": [
{ "_key": "color-1", "_type": "textColor", "color": "#3b82f6" }
]
}The same seam should also support non-color inline annotations (for example, a comment or citation) and simple decorators (for example, a named emphasis style).
Proposed scope
Could EmDash expose a native/trusted-plugin mark registration API, analogous to portableTextBlocks? The exact TypeScript shape is open for discussion, but a complete registration needs to cover:
- A stable Portable Text mark type and whether it is a decorator or annotation.
- The TipTap mark extension and an editor action/control for applying, editing, and removing it from selected text.
- Attribute validation plus Portable Text ↔ ProseMirror conversion, including
markDefskey creation/reuse. - Registration in both the Admin editor and the visual inline editor.
- An Astro
markComponentscontribution for published-site rendering, with the site's explicit component override retaining priority.
The first version can be native/trusted-only because TipTap, React controls, and Astro components execute application code. A declarative/sandboxed API could be considered separately. The API should not simply append arbitrary TipTap extensions while leaving the converters and safety checks unaware of them.
Acceptance criteria
- A native plugin can register a value-bearing
textColorannotation without patchingemdashor@emdash-cms/admin. - An editor can apply, change, and remove it on selected text in both editing surfaces. Saving and reopening preserves the text, other marks, validated attributes, and annotation keys where possible.
- The Admin, inline editor, server-side converters, and Astro renderer agree on the same Portable Text representation; tests cover round trips through each path, including marks alongside links and formatting.
- Duplicate/conflicting mark type registrations fail clearly.
- A missing, disabled, or invalid plugin mark keeps the existing #2493 protection: editing/saving is blocked with an actionable error, never silently stripped.
- Plugin-provided attributes are validated before rendering; a color example should accept a defined safe format rather than pass arbitrary CSS into
style.
Related work
- #2998 — highlight/text color or a mark registration seam
- #2430 / #2493 — unsupported-mark data-loss report and safety fix
- #1996 — lightweight editor extension point
- #144 / PR #143 — plugin-driven CSS-class editor styles, not merged
Would maintainers be open to a native-only mark registry as a first step, or prefer a narrower declarative inline-style API? A textColor proof of concept and round-trip tests could validate the agreed extension shape.
Source: emdash-cms/emdash