Skip to content

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:

  1. Extract Base JSON:
    Use the node’s exportJSON() method to retrieve all fundamental text node properties.

  2. Augment with Extended Properties:

    • Mutable Properties:
      Specify all custom mutable properties (for example, color and backgroundColor) in a _ext_props object. This enables Codox to recognize and synchronize these fields alongside core collaborative properties like text.
    • Immutable Metadata:
      Place all custom immutable properties in a codox_metadata object.
      Important: You must always include the extended node’s type (such as type: "warning-text") in codox_metadata. This is essential—Codox uses the type to fully identify the node and to preserve extended node types during deserialization and backward conversion.
  3. 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

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 node type) from the codox_metadata object.
    • Important: Always set the node’s type from codox_metadata.type to preserve the extended identity through the conversion.
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 from codox_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;
}
}