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 
__colorand__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,colorandbackgroundColor) in a_ext_propsobject. This enables Codox to recognize and synchronize these fields alongside core collaborative properties liketext. - Immutable Metadata:
Place all custom immutable properties in acodox_metadataobject.
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_propsfield with all new mutable properties - The 
codox_metadatafield 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 
styleand_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_propsobject. - Restoring immutable metadata (such as 
idand, crucially, the nodetype) from thecodox_metadataobject.- Important: Always set the node’s type from 
codox_metadata.typeto 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_metadataand_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;  }}