diff --git a/apps/web/src/main.css b/apps/web/src/main.css
index d4d0780e21e06bcf52273ff6eb6a671f4a8865a2..63faa31914c2f06e0ca4e7ecd9524020c9039f56 100644
--- a/apps/web/src/main.css
+++ b/apps/web/src/main.css
@@ -78,3 +78,19 @@ body {
   cursor: se-resize;
 }
 */
+
+.variable-node {
+  display: inline-block;
+  user-select: none;
+  padding: 3px 7px;
+  margin: 0px 7px;
+  font-size: 11px;
+  border-radius: 5px;
+  background: #e0e7f5;
+  color: #6e788e;
+  border: 1px solid #6e788e80;
+}
+
+p > span:first-child > .variable-node {
+  margin-left: 0px;
+}
\ No newline at end of file
diff --git a/libs/shared/lib/components/layout/Dialog.tsx b/libs/shared/lib/components/layout/Dialog.tsx
index 06ee3f226cdb4ed57c0d2d07e27d13916a315736..1f6bb731e679143a156b347d1267ded18b4d665d 100644
--- a/libs/shared/lib/components/layout/Dialog.tsx
+++ b/libs/shared/lib/components/layout/Dialog.tsx
@@ -141,7 +141,7 @@ export const DialogContent = React.forwardRef<HTMLDivElement, React.HTMLProps<HT
 
   return (
     <FloatingPortal>
-      <FloatingOverlay className="grid place-items-center z-50" lockScroll style={{ backgroundColor: 'rgba(0, 0, 0, 0.4)' }}>
+      <FloatingOverlay className="grid place-items-center" lockScroll style={{ backgroundColor: 'rgba(0, 0, 0, 0.4)' }}>
         <FloatingFocusManager context={context.floatingContext}>
           <div
             ref={ref}
diff --git a/libs/shared/lib/components/textEditor/Placeholder.tsx b/libs/shared/lib/components/textEditor/Placeholder.tsx
index 008398e8455f3f084e5e683e6948442c86f4d0a8..dbe71297b0b3dd5ee82b6ca0eee77a731379d110 100644
--- a/libs/shared/lib/components/textEditor/Placeholder.tsx
+++ b/libs/shared/lib/components/textEditor/Placeholder.tsx
@@ -1,5 +1,5 @@
 import React from 'react';
 
 export function Placeholder({ placeholder }: { placeholder?: string }) {
-  return placeholder && <div className="absolute inset-0 pointer-events-none flex items-center px-2 text-secondary-400">{placeholder}</div>;
+  return placeholder && <div className="absolute inset-0 pointer-events-none flex p-3 text-secondary-400">{placeholder}</div>;
 }
diff --git a/libs/shared/lib/components/textEditor/TextEditor.tsx b/libs/shared/lib/components/textEditor/TextEditor.tsx
index 8a95a80426a9b722c291c7cdf80b1c019b9bf77f..90f797e20f3f761638acf32f8bad7ef76ff58b6e 100644
--- a/libs/shared/lib/components/textEditor/TextEditor.tsx
+++ b/libs/shared/lib/components/textEditor/TextEditor.tsx
@@ -1,13 +1,17 @@
-import React from 'react';
+import { useRef } from 'react';
 import { LexicalComposer } from '@lexical/react/LexicalComposer';
 import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
 import { ContentEditable } from '@lexical/react/LexicalContentEditable';
 import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
-import { EditorState } from 'lexical';
+import { LexicalEditor, EditorState } from 'lexical';
+import { $generateHtmlFromNodes } from "@lexical/html";
 import { MyOnChangePlugin } from './plugins/StateChangePlugin';
 import { ToolbarPlugin } from './plugins/ToolbarPlugin';
+import { InsertVariablesPlugin } from './plugins/InsertVariablesPlugin';
 import { ErrorHandler } from './ErrorHandler';
 import { Placeholder } from './Placeholder';
+import { VariableNode } from './VariableNode';
+import { fontFamily } from 'html2canvas/dist/types/css/property-descriptors/font-family';
 
 type TextEditorProps = {
   editorState: EditorState | undefined;
@@ -17,14 +21,21 @@ type TextEditorProps = {
 };
 
 export function TextEditor({ editorState, setEditorState, showToolbar, placeholder }: TextEditorProps) {
-  function onChange(editorState: EditorState) {
+  function onChange(editorState: EditorState, editor: LexicalEditor) {
     setEditorState(editorState);
+    editor.read(() => {
+      const html = $generateHtmlFromNodes(editor)
+      text.current = html;
+    });
   }
 
+  const text = useRef<string>("hai");
+
   const initialConfig = {
     namespace: 'MyEditor',
     editorState: editorState,
     onError: ErrorHandler,
+    nodes: [ VariableNode ],
   };
 
   return (
@@ -32,12 +43,17 @@ export function TextEditor({ editorState, setEditorState, showToolbar, placehold
       {showToolbar && <ToolbarPlugin />}
       <div className="relative">
         <RichTextPlugin
-          contentEditable={<ContentEditable className="border min-h-24" />}
+          contentEditable={<ContentEditable className="border min-h-24 p-3" />}
           placeholder={<Placeholder placeholder={placeholder} />}
           ErrorBoundary={LexicalErrorBoundary}
         />
       </div>
       <MyOnChangePlugin onChange={onChange} />
+      <InsertVariablesPlugin />
+      <br /><b>Debug:</b>
+      <div style={{fontFamily: 'monospace'}}>
+        { text.current }
+      </div>
     </LexicalComposer>
   );
 }
diff --git a/libs/shared/lib/components/textEditor/VariableNode.tsx b/libs/shared/lib/components/textEditor/VariableNode.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..20fa9679572f1b8041e401affdc18df4ab5c384d
--- /dev/null
+++ b/libs/shared/lib/components/textEditor/VariableNode.tsx
@@ -0,0 +1,68 @@
+import type { NodeKey, LexicalEditor, DOMExportOutput } from 'lexical';
+import { DecoratorNode, EditorConfig } from 'lexical';
+
+
+export class VariableNode extends DecoratorNode<JSX.Element> {
+
+  __name: string;
+
+  static getType(): string {
+    return 'variable-node';
+  }
+
+  static clone(node: VariableNode): VariableNode {
+    return new VariableNode(node.__name, node.__key);
+  }
+
+  constructor(name: string, key?: NodeKey) {
+    super(key);
+    this.__name = name;
+  }
+
+  setName(name: string) {
+    const self = this.getWritable();
+    this.__name = name;
+  }
+
+  getName(): string {
+    const self = this.getLatest();
+    return self.__name;
+  }
+
+  getTextContent(): string {
+    const self = this.getLatest();
+    return `{{${self.__name}}}`;
+  }
+
+  // View
+
+  createDOM(config: EditorConfig): HTMLElement {
+    const span = document.createElement("span");
+    const theme = config.theme;
+    const className = theme.image;
+    if (className !== undefined) {
+      span.className = `${className}`;
+    }
+    return span;
+  }
+
+  updateDOM(): false {
+    return false;
+  }
+
+  exportDOM(editor: LexicalEditor): DOMExportOutput {
+    const self = this.getLatest();
+
+    return {
+      element: new Text(self.getTextContent())
+    };
+  }
+
+  decorate(): JSX.Element {
+    return (
+      <div className="variable-node">
+        {this.getName()}
+      </div>
+    );
+  }
+}
\ No newline at end of file
diff --git a/libs/shared/lib/components/textEditor/plugins/InsertVariablesPlugin.tsx b/libs/shared/lib/components/textEditor/plugins/InsertVariablesPlugin.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..baa419ee21901e969d84f266020b926bacb65744
--- /dev/null
+++ b/libs/shared/lib/components/textEditor/plugins/InsertVariablesPlugin.tsx
@@ -0,0 +1,55 @@
+import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
+import {
+  $copyNode,
+  $getRoot,
+  $isParagraphNode,
+  $isElementNode,
+  $getNodeByKey,
+  $getSelection,
+  type BaseSelection,
+  $parseSerializedNode
+} from "lexical";
+import { Input } from '@graphpolaris/shared/lib/components/inputs';
+import { VariableNode } from '../VariableNode';
+import { useState } from 'react';
+import { useGraphQueryResult } from '@graphpolaris/shared/lib/data-access';
+
+export const InsertVariablesPlugin = () => {
+  const [editor] = useLexicalComposerContext();
+
+  const onChange = (value: string | number) => {
+    editor.update(() => {
+      const selection = $getSelection() as BaseSelection;
+      
+      const node = new VariableNode(String(value));
+      
+      selection.insertNodes([node]);
+
+      // TODO: enable drag and dropping nodes
+    });
+  };
+
+  const result = useGraphQueryResult();
+
+  const nodeTypes = Object.keys(result.metaData?.nodes.types || {});
+  function optionsForType(type: string) {
+    const typeObj = result.metaData?.nodes.types[type] ?? {};
+    
+    if (!('attributes' in typeObj)) {
+      return [];
+    }
+    
+
+    return Object.entries(typeObj.attributes).map(([k,v]) => Object.keys(v).map(x => `${k}_${x}`)).flat();
+  }
+
+  return nodeTypes.map((nodeType) => 
+      <Input
+            type="dropdown"
+            label={`${nodeType} variable`}
+            value=""
+            options={optionsForType(nodeType)}
+            onChange={onChange}
+            />
+        );
+};
\ No newline at end of file
diff --git a/libs/shared/lib/components/textEditor/plugins/StateChangePlugin.tsx b/libs/shared/lib/components/textEditor/plugins/StateChangePlugin.tsx
index c4ef00a4e0f172acc57bec4d7b53416b25c70524..bf3f902de691a8a2862d0b371313013646d7d477 100644
--- a/libs/shared/lib/components/textEditor/plugins/StateChangePlugin.tsx
+++ b/libs/shared/lib/components/textEditor/plugins/StateChangePlugin.tsx
@@ -1,13 +1,13 @@
 import React, { useEffect } from 'react';
 import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
-import { EditorState } from 'lexical';
+import { LexicalEditor, EditorState } from 'lexical';
 
-export function MyOnChangePlugin({ onChange }: { onChange: (val: EditorState) => void }) {
+export function MyOnChangePlugin({ onChange }: { onChange: (val: EditorState, editor: LexicalEditor) => void }) {
   const [editor] = useLexicalComposerContext();
 
   useEffect(() => {
     return editor.registerUpdateListener(({ editorState }) => {
-      onChange(editorState);
+      onChange(editorState, editor);
     });
   }, [editor, onChange]);
 
diff --git a/libs/shared/lib/components/textEditor/plugins/ToolbarPlugin.tsx b/libs/shared/lib/components/textEditor/plugins/ToolbarPlugin.tsx
index 93c56d75cff092de4ed746d9d3465fdc0e57ed5a..74ded236745eb6bb53d66ce13e88136482294cbd 100644
--- a/libs/shared/lib/components/textEditor/plugins/ToolbarPlugin.tsx
+++ b/libs/shared/lib/components/textEditor/plugins/ToolbarPlugin.tsx
@@ -27,7 +27,7 @@ export function ToolbarPlugin() {
         variant="ghost"
         size="xs"
         iconComponent="icon-[ic--baseline-format-underlined]"
-        onClick={formatItalic}
+        onClick={formatUnderline}
       />
     </div>
   );