headless lms

Plate editor

Notion-style activity content editing with the content-plate plugin.

@headless-lms/content-plate is the Plate-based implementation of the swappable activity content editor. It default-exports an EditorModule satisfying @headless-lms/editor; the content blob is the Plate node array, stored opaquely by the backend — the server never interprets it.

What's included

  • Text blocks — paragraphs, headings, blockquote, code blocks with syntax highlighting, bulleted/numbered/to-do lists, toggle, callout, columns, tables, dates, emoji, links, font and background color.
  • Media — image, video, audio, file and URL embeds, with captions, resize, preview, and upload placeholders (uploads go through the storage adapter's presigned URLs).
  • Notion-style UX — / slash command menu, drag handles and drop targets, multi-block selection, right-click block context menu (turn into, color, align, duplicate, delete), floating formatting toolbar, and autoformat shortcuts (#, >, ```, -, …).
  • Host slots — given a toolbar slot, a fixed toolbar that switches between text formatting and media options for the selected block replaces the floating one; given a blocks slot, a block library where blocks are clicked in at the cursor or dragged onto the page.

Swapping the editor

The editor is behind @headless-lms/editor: an EditorModule bundles the client-side Editor, an RSC-safe Renderer, an optional validate function, a required assetIds function, and meta: { type, version } identifying the content format. Implement the contract and register your module in the frontend to replace Plate entirely — existing content keeps working as long as your module declares the format it can render. See Custom editor for the full contract.

assetIds(config) is a pure function that returns the ids of the media library files a config shows: every image, video, audio or file node that names a host asset. When an activity's content is saved, the admin sends exactly these ids as the activity's asset links. The server accepts only ready files from the org's media library and refuses the save, naming the ids, otherwise. An editor that embeds no host assets returns []. The Plate editor collects every node's assetId.

The API returns each activity with assetUrls: a stable, access-checked URL for every file the activity links, keyed by asset id. The host passes that map to the Editor and to the Renderer as assetUrls. A config stores the asset id of a media node, never its URL: the editor removes URLs from every config it hands to onSave and onChange, and a media node shows the URL from the map. Only linked files get a URL, so a file the editor shows but assetIds leaves out never reaches students.

A host that lays out the editing screen itself passes slots to the Editor: DOM elements for the toolbar and the blocks library. An editor lists the slots it fills in EditorModule.slots, and the admin shows the matching toolbar and Blocks panel only for those.

On this page