Editors Plugin
@vibeflui/editors provides rich text, code, markdown, and Editor.js field adapters.
Install
Install the package and the peer editor libraries used by the adapters you enable.
npm install @vibeflui/core @vibeflui/editorsFor rich text:
npm install @tiptap/react @tiptap/starter-kit @tiptap/pmFor code:
npm install @uiw/react-codemirrorFor markdown:
npm install @uiw/react-md-editorFor Editor.js:
npm install @editorjs/editorjsPublic exports
| Export | Purpose |
|---|---|
richtextPlugin | Registers the richtext field adapter. |
codePlugin | Registers the code field adapter. |
markdownPlugin | Registers the markdown field adapter. |
editorJsPlugin | Registers the editorjs field adapter. Accepts { tools } for Editor.js tool packages (Header, List, etc.). |
createEditorsPlugin | Registers richtext, code, markdown, and editorjs together. |
editorsPlugin | Prebuilt plugin from createEditorsPlugin(). |
RichTextField | TipTap field adapter component. |
CodeField | CodeMirror field adapter component. |
MarkdownField | react-md-editor field adapter component. |
EditorJsField | Editor.js field adapter component. |
createEditorJsField | Factory returning an EditorJsField with specific tools baked in. |
EditorJsEditor | Standalone Editor.js component — usable outside <FluiKit>/<FluiKitForm> as a plain controlled component. |
buildEditorJsTools | Wires app-imported @editorjs/* tool classes (Header, List, Image, ...) into a ready-to-use tools config, applying sensible per-tool defaults and image/link endpoint wiring. |
applyEditorJsFieldAdapterProps | Applies a field's adapterProps (tool allowlist, endpoint overrides) on top of an already-built tools object. Used internally by EditorJsField. |
EDITORJS_DEFAULT_TOOL_NAMES | The list of tool names buildEditorJsTools understands (header, list, checklist, quote, warning, delimiter, table, code, embed, image, linkTool, raw, marker, inlineCode, underline). |
Registered slots
| Plugin | Field adapter keys |
|---|---|
richtextPlugin() | richtext |
codePlugin() | code |
markdownPlugin() | markdown |
editorJsPlugin() | editorjs |
createEditorsPlugin() | richtext, code, markdown, editorjs |
Minimal setup
Prefer per-editor plugins when the app only needs one editor.
import { FluiKit, FluiKitProvider } from "@vibeflui/core";
import { markdownPlugin } from "@vibeflui/editors";
const editorPlugin = markdownPlugin();
export function ArticlePage() {
return (
<FluiKitProvider plugins={[editorPlugin]}>
<FluiKit schema={articleSchema} />
</FluiKitProvider>
);
}Schema usage
export const articleSchema = {
resource: "articles",
form: {
fields: [
{ name: "body", label: "Body", type: "richtext", adapter: "richtext" },
{ name: "config", label: "Config", type: "code", adapter: "code" },
{ name: "notes", label: "Notes", type: "markdown", adapter: "markdown" },
{ name: "content", label: "Content", type: "json", adapter: "editorjs" }
]
},
registry: {
fieldAdapters: ["richtext", "code", "markdown", "editorjs"]
}
} as const;Editor.js
Editor.js is a block editor. Its field value is Editor.js's own OutputData shape ({ time, blocks, version }), not a string — that is why it pairs with type: "json" instead of a dedicated field type, matching the json field type's existing "textarea unless an adapter overrides it" contract.
import { FluiKitProvider } from "@vibeflui/core";
import { editorJsPlugin } from "@vibeflui/editors";
<FluiKitProvider plugins={[editorJsPlugin()]}>
<FluiKit schema={articleSchema} />
</FluiKitProvider>With no tools configured, this only enables Editor.js's built-in tools: paragraph blocks plus inline Bold/Italic/Link. Every other block — Header, List, Checklist, Quote, Warning, Table, Code, Embed, Image, Link preview, Raw HTML — and the Marker/Inline Code/Underline inline tools are each a separate @editorjs/* npm package that must be installed and wired explicitly.
Enabling the full tool set
@vibeflui/editors never imports these tool packages itself — install the ones you want, import their classes in your own registry file, and wire them with buildEditorJsTools:
npm install @editorjs/header @editorjs/list @editorjs/checklist @editorjs/quote \
@editorjs/warning @editorjs/delimiter @editorjs/table @editorjs/code \
@editorjs/embed @editorjs/image @editorjs/link @editorjs/raw \
@editorjs/marker @editorjs/inline-code @editorjs/underlineimport Header from "@editorjs/header";
import EditorjsList from "@editorjs/list";
import Checklist from "@editorjs/checklist";
import Quote from "@editorjs/quote";
import Warning from "@editorjs/warning";
import Delimiter from "@editorjs/delimiter";
import EditorjsTable from "@editorjs/table";
import CodeTool from "@editorjs/code";
import Embed from "@editorjs/embed";
import ImageTool from "@editorjs/image";
import LinkTool from "@editorjs/link";
import RawTool from "@editorjs/raw";
import Marker from "@editorjs/marker";
import InlineCode from "@editorjs/inline-code";
import Underline from "@editorjs/underline";
import { buildEditorJsTools, editorJsPlugin } from "@vibeflui/editors";
const editorJsTools = buildEditorJsTools(
{
header: Header,
list: EditorjsList,
checklist: Checklist,
quote: Quote,
warning: Warning,
delimiter: Delimiter,
table: EditorjsTable,
code: CodeTool,
embed: Embed,
image: ImageTool,
linkTool: LinkTool,
raw: RawTool,
marker: Marker,
inlineCode: InlineCode,
underline: Underline
},
{
image: { endpointByFile: "/api/editorjs/upload-image" },
link: { endpoint: "/api/editorjs/link-preview" }
}
);
const plugin = editorJsPlugin({ tools: editorJsTools });buildEditorJsTools only registers a tool whose class was actually passed in — leave one out to exclude it. It applies sensible per-tool defaults (inline toolbar where it fits, Header's levels, Embed's common service list) so callers don't need to know each tool's raw config shape. Tool classes still cannot live in a JSON-friendly schema, so this wiring happens at plugin registration.
Narrowing tools per schema field
field.adapterProps is JSON-friendly (plain strings/URLs, no classes), so it can safely narrow the enabled tool set or override the image/link endpoint for one specific field, without a second plugin registration:
{
name: "body",
label: "Body",
type: "json",
adapter: "editorjs",
adapterProps: {
tools: ["header", "list", "quote", "linkTool"]
}
}Image upload and link preview endpoints
The Image and Link preview tools call a server endpoint — VibeFlui only owns the FE/UI layer and does not provide one, but the request/response contract is fixed by Editor.js:
- Image (
endpointByFile): receives amultipart/form-dataupload under thefieldname ("file"by default). Must respond{ "success": 1, "file": { "url": "https://..." } }. - Link preview (
endpoint): receives a GET request with the target URL as?url=. Must respond{ "success": 1, "link": "https://...", "meta": { "title": "...", "description": "...", "image": { "url": "https://..." } } }— the server fetches the URL and parses its Open Graph tags to buildmeta.
buildEditorJsTools skips registering image/linkTool entirely when no endpoint is configured, instead of shipping a tool that would fail on first use.
Using Editor.js outside a form
EditorJsEditor is a plain controlled component. It does not need <FluiKit>, <FluiKitForm>, or a registry — use it directly anywhere in a React tree:
"use client";
import { useState } from "react";
import { EditorJsEditor, type EditorJsOutputData } from "@vibeflui/editors";
import { editorJsTools } from "@/lib/vibeflui/editorjs-tools";
export function ArticleBodyEditor() {
const [value, setValue] = useState<EditorJsOutputData>();
return <EditorJsEditor value={value} onChange={setValue} tools={editorJsTools} />;
}Omit tools and the standalone editor also falls back to paragraph plus inline Bold/Italic/Link only.
Markdown stylesheet
The markdown adapter source notes that the editor stylesheet should be imported once.
import "@uiw/react-md-editor/markdown-editor.css";Common mistakes
- Use
type: "richtext"andadapter: "richtext"for the current rich text adapter API. - Do not use
createEditorsPlugin()unless all peer editor libraries are installed. - Do not try to pass Editor.js tool classes through the schema — schemas must stay JSON-friendly; import
@editorjs/*packages and buildtoolswithbuildEditorJsToolsat plugin registration instead.field.adapterProps.tools(a string-name allowlist) and its.image/.linkendpoint overrides are schema-safe. type: "json"alone renders a plain textarea;adapter: "editorjs"is what actually swaps in the Editor.js block editor.- Do not assume Header/List/Image/etc. render with zero setup — without
tools, Editor.js only renders paragraph blocks plus inline Bold/Italic/Link.