#3168·emdash

Feature request: Let native plugins register Portable Text inline marks

Author: b1ngxCreated Sep 17, 2026Updated Sep 17, 2026
Labelsarea/adminarea/corearea/pluginsbot:enhancementbot:awaiting-approval

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:

json
{
  "_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:

  1. A stable Portable Text mark type and whether it is a decorator or annotation.
  2. The TipTap mark extension and an editor action/control for applying, editing, and removing it from selected text.
  3. Attribute validation plus Portable Text ↔ ProseMirror conversion, including markDefs key creation/reuse.
  4. Registration in both the Admin editor and the visual inline editor.
  5. An Astro markComponents contribution 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 textColor annotation without patching emdash or @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.