Create the component

Let’s dive into an example by creating a custom node called TextUpdaterNode. For this, we’ve added a simple input field with a change handler.

export function TextUpdaterNode(props) {
  const onChange = useCallback((evt) => {
    console.log(evt.target.value);
  }, []);
 
  return (
    <div className="text-updater-node">
      <div>
        <label htmlFor="text">Text:</label>
        <input id="text" name="text" onChange={onChange} className="nodrag" />
      </div>
    </div>
  );
}
Using TypeScript? You can head on over to our [TypeScript guide](/learn/advanced-use/typescript#custom-nodes) to learn how to set up your custom nodes with the right types. This will make sure you'll have typed access to your node's props and `data`.
Initialize nodeTypes

You can add a new node type to React Flow by adding it to the nodeTypes prop like below. We define the nodeTypes outside of the component to prevent re-renderings.

const nodeTypes = {
  textUpdater: TextUpdaterNode,
};
Pass nodeTypes to React Flow
<ReactFlow
  nodes={nodes}
  edges={edges}
  nodeTypes={nodeTypes}
  onNodesChange={onNodesChange}
  onEdgesChange={onEdgesChange}
  fitView
/>
Update node definitions

After defining your new node type, you can use it by specifying the type property on your node definition:

const nodes = [
  {
    id: 'node-1',
    type: 'textUpdater',
    position: { x: 0, y: 0 },
    data: { value: 123 },
  },
];
Flow with custom node

After putting all together and adding some basic styles we get a custom node that prints text to the console:

Example: learn/custom-node

App.jsx
import { useState } from 'react';
import { ReactFlow } from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import TextUpdaterNode from './TextUpdaterNode';
 
const rfStyle = {
  backgroundColor: '#B8CEFF',
};
 
const initialNodes = [
  {
    id: 'node-1',
    type: 'textUpdater',
    position: { x: 0, y: 0 },
    data: { value: 123 },
  },
];
// we define the nodeTypes outside of the component to prevent re-renderings
// you could also use useMemo inside the component
const nodeTypes = { textUpdater: TextUpdaterNode };
 
function Flow() {
  const [nodes, setNodes] = useState(initialNodes);
  const [edges, setEdges] = useState([]);
 
  return (
    <ReactFlow
      nodes={nodes}
      edges={edges}
      nodeTypes={nodeTypes}
      fitView
      style={rfStyle}
      colorMode="system"
    />
  );
}
 
export default Flow;
TextUpdaterNode.jsx
import { useCallback } from 'react';
 
function TextUpdaterNode(props) {
  const onChange = useCallback((evt) => {
    console.log(evt.target.value);
  }, []);
 
  return (
    <div className="text-updater-node">
      <div>
        <label htmlFor="text">Text:</label>
        <input id="text" name="text" onChange={onChange} className="nodrag" />
      </div>
    </div>
  );
}
 
export default TextUpdaterNode;
index.css
html,
body {
  margin: 0;
  font-family: sans-serif;
}
 
#app {
  width: 100vw;
  height: 100vh;
}
 
.text-updater-node {
  height: 50px;
  border: 1px solid #eee;
  padding: 5px;
  border-radius: 5px;
  background: white;
}
 
.text-updater-node label {
  display: block;
  color: #777;
  font-size: 12px;
}
index.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>React Flow Example</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="./index.jsx"></script>
  </body>
</html>
index.jsx
import { createRoot } from 'react-dom/client';
import App from './App';
 
import './index.css';
 
const container = document.querySelector('#app');
const root = createRoot(container);
 
root.render(<App />);

Full code example 🏁

Example: learn/custom-node

App.jsx
import { useState } from 'react';
import { ReactFlow } from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import TextUpdaterNode from './TextUpdaterNode';
 
const rfStyle = {
  backgroundColor: '#B8CEFF',
};
 
const initialNodes = [
  {
    id: 'node-1',
    type: 'textUpdater',
    position: { x: 0, y: 0 },
    data: { value: 123 },
  },
];
// we define the nodeTypes outside of the component to prevent re-renderings
// you could also use useMemo inside the component
const nodeTypes = { textUpdater: TextUpdaterNode };
 
function Flow() {
  const [nodes, setNodes] = useState(initialNodes);
  const [edges, setEdges] = useState([]);
 
  return (
    <ReactFlow
      nodes={nodes}
      edges={edges}
      nodeTypes={nodeTypes}
      fitView
      style={rfStyle}
      colorMode="system"
    />
  );
}
 
export default Flow;
TextUpdaterNode.jsx
import { useCallback } from 'react';
 
function TextUpdaterNode(props) {
  const onChange = useCallback((evt) => {
    console.log(evt.target.value);
  }, []);
 
  return (
    <div className="text-updater-node">
      <div>
        <label htmlFor="text">Text:</label>
        <input id="text" name="text" onChange={onChange} className="nodrag" />
      </div>
    </div>
  );
}
 
export default TextUpdaterNode;
index.css
html,
body {
  margin: 0;
  font-family: sans-serif;
}
 
#app {
  width: 100vw;
  height: 100vh;
}
 
.text-updater-node {
  height: 50px;
  border: 1px solid #eee;
  padding: 5px;
  border-radius: 5px;
  background: white;
}
 
.text-updater-node label {
  display: block;
  color: #777;
  font-size: 12px;
}
index.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>React Flow Example</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="./index.jsx"></script>
  </body>
</html>
index.jsx
import { createRoot } from 'react-dom/client';
import App from './App';
 
import './index.css';
 
const container = document.querySelector('#app');
const root = createRoot(container);
 
root.render(<App />);

To enable your custom node to connect with other nodes, check out the Handles page to learn how to add source and target handles.