Supporting Custom Lexical Nodes in Codox
Codox natively supports core Lexical nodes and official playground node types. When you need node behaviors beyond these defaults, you can create custom nodes tailored to your application’s requirements.
This guide covers:
- Extension: How to extend Lexical nodes
- Serialization: Converting custom Lexical nodes to a Codox-compatible JSON format
- Deserialization: Restoring a Codox JSON node back to Lexical format
When to use custom nodes
Before creating a custom node, review the supported Lexical core and official playground nodes to find one that matches your needed behavior. Custom nodes should be implemented only when:
- No existing node matches the required functionality
- You need to extend an existing node with custom properties that require synchronization
Official playground nodes are examples and may be adapted for your specific app’s UI. Prefer using supported nodes when possible.
Extending a text node to work with Codox
1. Setting Up the Extended Node
In this example, WarningTextNode
is a custom node that extends the standard TextNode
.
A TextNode
includes several properties, some of which are mutable—meaning they can be edited and synced during collaboration—and others are immutable—acting as stable metadata and generally unchanged by users (see node definition).
-
Mutable properties:
text
: The core string content, modifiable by users and updated during collaboration.format
: A comma-separated list of format values (e.g.,bold,italic
), each toggled individually by users.style
: A colon-separated list of CSS property-value pairs (e.g.,"padding:5px;font-weight:400;"
), each editable via the UX.
-
Immutable properties:
mode
,detail
: Node configuration or structural information; typically constant and not modifiable by user action.
When extending TextNode
, you can add both mutable and immutable custom properties:
- Immutable custom metadata (such as a node identifier:
__id
) - Mutable style properties (such as
__color
and__backgroundColor
)
This setup demonstrates how to extend a Lexical base node: simply add custom properties based on whether they need to be synchronized (mutable) or serve as fixed metadata (immutable).
// Private instance fields for the extension__id; // immutable identifier__color; // mutable text color (CSS color string)__backgroundColor; // mutable background color (CSS color string)
constructor(id, color, backgroundColor, text, key) { // invoke parent constructor super(text, key);
// extension: set custom attributes, with default values this.__id = id || 'mock_id'; this.__color = color || '#ffffff'; this.__backgroundColor = backgroundColor || '#ff4902'; }
exportJSON() { return { // invoke parent text node export ...super.exportJSON(), version: 1,
/** * extension: attach custom properties to the exported node */ id: this.__id, color: this.__color, backgroundColor: this.__backgroundColor, type: 'warning-text', // custom node type
}; }
/* * Override TextNode createDOM */ createDOM(config) { //Invoke parent createDOM const dom = super.createDOM(config);
/** * extension: Compose custom css styles string from custom attributes */ dom.style.cssText = `color:${this.__color};background-color:${this.__backgroundColor};`; dom.className = 'warning-text-frame';
return dom; }
2. Converting from Lexical to Codox Format
After a custom node has been extended and its interactions have been implemented, the next step is integrating it with Codox. Codox must be informed of which properties are mutable (must be merged and synchronized during collaboration) and which are immutable (should not be changed or synced).
Implementing toCodoxNode
The toCodoxNode
method transforms a Lexical node into a Codox-compatible JSON object. The process involves:
-
Extract Base JSON:
Use the node’sexportJSON()
method to retrieve all fundamental text node properties. -
Augment with Extended Properties:
- Mutable Properties:
Specify all custom mutable properties (for example,color
andbackgroundColor
) in a_ext_props
object. This enables Codox to recognize and synchronize these fields alongside core collaborative properties liketext
. - Immutable Metadata:
Place all custom immutable properties in acodox_metadata
object.
Important: You must always include the extended node’stype
(such astype: "warning-text"
) incodox_metadata
. This is essential—Codox uses the type to fully identify the node and to preserve extended node types during deserialization and backward conversion.
- Mutable Properties:
-
Construct the Codox Node:
The Codox node JSON must contain:- Standard base attributes (
text
,format
,mode
,detail
,version
) - The
_ext_props
field with all new mutable properties - The
codox_metadata
field containing the extended node type and all immutable data
- Standard base attributes (
Example Code Snippet:
/** * Implement the following API methods to interoperate with Codox * Lexical -> Codox Node * Converts lexical node to json node, acceptable by Codox. */ toCodoxNode() { /** * Step 1: Get original text node json. You should/must use .exportJSON() */ const originalJsonNode = this.exportJSON();
/** * Step 2: Extend with additional properties. */ const convertedNode = { type: 'text', // type property must be one of core or playground node types
// preserve default text node attributes text: originalJsonNode.text, format: originalJsonNode.format, mode: originalJsonNode.mode, detail: originalJsonNode.detail, version: originalJsonNode.version, style: originalJsonNode.style, /** * Note: for block nodes, preserve children like this: * children: originalJsonNode.children */
/** * extension: codox_metadata is REQUIRED and should hold immutables: * - required: your extended node type, e.g. 'warning-text' * - optional: any immutable properties of an original node - these fields are not merged and synchronized. */ codox_metadata: { type: originalJsonNode.type, //REQUIRED. should hold custom type: type == 'warning-text' id: originalJsonNode.id, //some custom id an immutable property }, /** * extension: hold mutable custom properties: */ _ext_props: { color: originalJsonNode.color, backgroundColor: originalJsonNode.backgroundColor, }, }; // return the converted node return convertedNode; }
Key Points
Immutable vs. mutable (at a glance)
- MUST include
codox_metadata.type
= your original custom node type (e.g.,"warning-text"
) to preserve identity on deserialization. - Keep immutable IDs in
codox_metadata
(not merged). - Put user-editable extension fields in
_ext_props
(merged). - If the same data appears in both
style
and_ext_props
, document a single source of truth (recommended:_ext_props
).
3. Converting from Codox to Lexical Format
After Codox has synchronized a node, your application must reconstruct it as a Lexical-compatible representation. This is achieved by implementing a static
method that reverses the marshalling process.
Implementing fromCodoxNode
This method deserializes a codoxNode
back into Lexical format by:
- Extracting custom mutable properties (such as
color
,backgroundColor
) from the_ext_props
object. - Restoring immutable metadata (such as
id
and, crucially, the nodetype
) from thecodox_metadata
object.- Important: Always set the node’s type from
codox_metadata.type
to preserve the extended identity through the conversion.
- Important: Always set the node’s type from
Rebuilding the Lexical Node JSON
Create a new JSON object that matches the Lexical node structure, including:
- Base attributes:
text
,format
,mode
,style
,detail
,version
- Extended attributes: the custom id, color, background color, and node type
(extracted fromcodox_metadata
and_ext_props
, respectively).
This guarantees all collaborative changes and identifying metadata are correctly restored, maintaining both function and identity for future operations with Codox and Lexical.
Example Code Snippet:
/** * Implement the following API methods to interoperate with Codox * Codox -> Lexical Node * Converts synchronized codox json node to lexical json node * must be class 'static' method */ static fromCodoxNode(codoxNode) { // compose original json node from codox node const originalJsonNode = { // set original type - in this example it will be "warning-text" type: codoxNode.codox_metadata.type, // extract original type from codox_metadata // set base attributes text: codoxNode.text, format: codoxNode.format, mode: codoxNode.mode, detail: codoxNode.detail, version: codoxNode.version, style: codoxNode.style, /** * Note: for block nodes, preserve children like this: * children: codoxNode.children */
/** * set custom attributes */ id: codoxNode.codox_metadata.id, // extract immutable id from codox_metadata color: codoxNode._ext_props.color, // extract mutable custom color from _ext_props backgroundColor: codoxNode._ext_props.backgroundColor,// extract mutable custom background color from _ext_props }; return originalJsonNode; }
Full Example Code Snippet
Puting alltogether:
class WarningTextNode extends TextNode { /** * Assume you want to add the following attributes to an extended node */ __id; //some custom id that's immutable __color; //color attribute that holds a string, and is mutable __backgroundColor; //background color attribute that holds a string, and is mutable
constructor(id, color, backgroundColor, text, key) { // invoke parent constructor super(text, key);
// extension: set custom attributes, with default values this.__id = id || 'mock_id'; this.__color = color || '#ffffff'; this.__backgroundColor = backgroundColor || '#ff4902'; }
static getType() { return 'warning-text'; // custom node type }
static importJSON(serializedNode) { const warningTextNode = new WarningTextNode( serializedNode.id, serializedNode.color, serializedNode.backgroundColor, serializedNode.text ); // preserve deault text node properties warningTextNode.setFormat(serializedNode.format); warningTextNode.setDetail(serializedNode.detail); warningTextNode.setMode(serializedNode.mode); warningTextNode.setStyle(serializedNode.style); return warningTextNode; }
exportJSON() { return { // invoke parent text node export ...super.exportJSON(), version: 1,
/** * extension: attach custom properties to the exported node */ id: this.__id, color: this.__color, backgroundColor: this.__backgroundColor, type: 'warning-text', // custom node type }; }
/* * Override TextNode createDOM */ createDOM(config) { //Invoke parent createDOM const dom = super.createDOM(config);
/** * extension: Compose custom css styles string from custom attributes */ dom.style.cssText = `color:${this.__color};background-color:${this.__backgroundColor};`; dom.className = 'warning-text-frame';
return dom; }
// ... rest of node api...
/** * Implement the following API methods to interoperate with Codox * Lexical -> Codox Node * Converts lexical node to json node, acceptable by Codox. */ toCodoxNode() { /** * Step 1: Get original text node json. You should/must use .exportJSON() */ const originalJsonNode = this.exportJSON();
/** * Step 2: Extend with additional properties. */ const convertedNode = { type: 'text', // type property must be one of core or playground node types
// preserve default text node attributes text: originalJsonNode.text, format: originalJsonNode.format, mode: originalJsonNode.mode, detail: originalJsonNode.detail, version: originalJsonNode.version, style: originalJsonNode.style
/** * extension: codox_metadata is REQUIRED and should hold immutables: * - required: your extended node type, e.g. 'warning-text' * - optional: any immutable properties of an original node - these fields are not merged and synchronized. */ codox_metadata: { type: originalJsonNode.type, //REQUIRED. should hold custom type: type == 'warning-text' id: originalJsonNode.id, //some custom id an immutable property }, /** * extension: _ext_props is optional should hold mutable custom properties: */ _ext_props: { // put custom mutable props here color: originalJsonNode.color, backgroundColor: originalJsonNode.backgroundColor, }, }; // return the converted node return convertedNode; }
/** * Implement the following API methods to interoperate with Codox * Codox -> Lexical Node * Converts synchronized codox json node to lexical json node * must be class 'static' method */ static fromCodoxNode(codoxNode) { // compose original json node from codox node const originalJsonNode = { // set original type - in this example it will be "warning-text" type: codoxNode.codox_metadata.type, // extract original type from codox_metadata // set base attributes text: codoxNode.text, format: codoxNode.format, mode: codoxNode.mode, detail: codoxNode.detail, version: codoxNode.version, style: codoxNode.style,
/** * set custom attributes */ id: codoxNode.codox_metadata.id, // extract immutable id from codox_metadata color: codoxNode._ext_props.color, // extract mutable custom color from _ext_props backgroundColor: codoxNode._ext_props.backgroundColor,// extract mutable custom background color from _ext_props }; return originalJsonNode; }}