Skip to main content

Rich-text links + isLocalizable is now immutable once content exists

· 2 min read

A new inline mark for rich text and a content-modeling guardrail landed this round.

Rich text gained a link mark, so a run of text can now carry a hyperlink end to end — from the TipTap editor through storage, validation, and both delivery projections.

The mark sits alongside the existing formatting marks and adds two fields:

{ "type": "link", "href": "/about", "title": "About us" }
  • href is required. It must be an absolute http/https URL, a mailto:/tel: link, or a relative path. Unsafe schemes like javascript: are rejected on write, and sanitized again at render time as defense in depth.
  • title is optional and maps to the anchor's title attribute.

The html projection renders <a href="…">, adding rel="noopener noreferrer" on external links (and a title attribute when present). This works identically on the GraphQL and REST delivery paths. The HTML-paste cleaner (from-html) preserves anchors, so pasting from Word / Google Docs keeps links intact.

The href is a plain string today; the AST shape leaves room for a future entry-reference link variant without breaking stored content.

React

@krios/react's KriosRichText handles the link mark out of the box — the default renderer receives href + optional title, sanitizes the href, and adds rel="noopener noreferrer" on external links. Override it like any other mark renderer for full control.

See Rich Text for the full AST reference.

isLocalizable is now immutable once a field has content

Shared and per-locale values use different storage keys, so flipping isLocalizable after a field already has data would orphan the existing values. The field-update API now guards against this: changing the flag once any value has been written returns 409 is_localizable_immutable. Set it correctly at field creation time.

isSortable and isFilterable remain mutable. See Flags for the full matrix.