Skip to main content
Custom nodes allow you to extend Flowise with new functionality. This guide covers the structure, development, and best practices for creating nodes.

Node Structure

All custom nodes are located in packages/concepts/nodes-and-edges/nodes/. Nodes are organized by category:
  • chatmodels/ - Language model integrations
  • tools/ - Agent tools and utilities
  • embeddings/ - Text embedding models
  • vectorstores/ - Vector database integrations
  • documentloaders/ - Document loading utilities
  • chains/ - LangChain workflows
  • agents/ - Agent implementations
  • memory/ - Conversation memory types
  • utilities/ - Helper nodes

Basic Node Template

Every node implements the INode interface:
import { INode, INodeData, INodeParams } from '../../../src/Interface'
import { getBaseClasses } from '../../../src/utils'

class MyCustomNode implements INode {
    label: string
    name: string
    version: number
    description: string
    type: string
    icon: string
    category: string
    baseClasses: string[]
    inputs: INodeParams[]
    outputs?: INodeOutputsValue[]

    constructor() {
        this.label = 'My Custom Node'
        this.name = 'myCustomNode'
        this.version = 1.0
        this.type = 'MyCustomNode'
        this.icon = 'icon.svg'
        this.category = 'Tools'
        this.description = 'Description of what this node does'
        this.baseClasses = [this.type]
        this.inputs = [
            // Define inputs here
        ]
    }

    async init(nodeData: INodeData, input: string, options: ICommonObject): Promise<any> {
        // Initialize and return your node instance
    }
}

module.exports = { nodeClass: MyCustomNode }

Node Properties

Required Properties

PropertyTypeDescription
labelstringDisplay name shown in the UI
namestringUnique identifier (camelCase)
versionnumberNode version for updates
typestringNode type identifier
categorystringCategory for organization
descriptionstringBrief explanation of functionality
baseClassesstring[]Supported base classes
inputsINodeParams[]Input parameters

Optional Properties

PropertyTypeDescription
iconstringIcon filename (in public folder)
credentialINodeParamsCredential requirements
outputsINodeOutputsValue[]Output definitions
loadMethodsobjectAsync methods for loading options

Defining Inputs

Inputs define the parameters users can configure:
this.inputs = [
    {
        label: 'API Key',
        name: 'apiKey',
        type: 'password',
        optional: false
    },
    {
        label: 'Model Name',
        name: 'modelName',
        type: 'options',
        options: [
            { label: 'GPT-4', name: 'gpt-4' },
            { label: 'GPT-3.5', name: 'gpt-3.5-turbo' }
        ],
        default: 'gpt-3.5-turbo'
    },
    {
        label: 'Temperature',
        name: 'temperature',
        type: 'number',
        default: 0.7,
        step: 0.1,
        optional: true
    },
    {
        label: 'Configuration',
        name: 'config',
        type: 'json',
        optional: true,
        additionalParams: true
    }
]

Input Types

  • string - Text input
  • number - Numeric input with optional step
  • boolean - Checkbox
  • password - Masked text input
  • options - Dropdown selection
  • multiOptions - Multiple selection
  • asyncOptions - Dynamically loaded options
  • json - JSON editor
  • code - Code editor
  • file - File upload
  • folder - Folder selection

Example: Simple Calculator Tool

Here’s a complete example of a basic calculator node:
Calculator.ts
import { INode } from '../../../src/Interface'
import { getBaseClasses } from '../../../src/utils'
import { Calculator } from '@langchain/community/tools/calculator'

class Calculator_Tools implements INode {
    label: string
    name: string
    version: number
    description: string
    type: string
    icon: string
    category: string
    baseClasses: string[]

    constructor() {
        this.label = 'Calculator'
        this.name = 'calculator'
        this.version = 1.0
        this.type = 'Calculator'
        this.icon = 'calculator.svg'
        this.category = 'Tools'
        this.description = 'Perform calculations on response'
        this.baseClasses = [this.type, ...getBaseClasses(Calculator)]
    }

    async init(): Promise<any> {
        return new Calculator()
    }
}

module.exports = { nodeClass: Calculator_Tools }

Example: Custom API Tool

A more complex example with inputs and credentials:
CustomAPI.ts
import { INode, INodeData, INodeParams, ICommonObject } from '../../../src/Interface'
import { getCredentialData, getCredentialParam } from '../../../src/utils'
import { DynamicStructuredTool } from '@langchain/core/tools'
import { z } from 'zod'

class CustomAPI_Tools implements INode {
    label: string
    name: string
    version: number
    description: string
    type: string
    icon: string
    category: string
    baseClasses: string[]
    credential: INodeParams
    inputs: INodeParams[]

    constructor() {
        this.label = 'Custom API'
        this.name = 'customAPI'
        this.version = 1.0
        this.type = 'CustomAPI'
        this.icon = 'api.svg'
        this.category = 'Tools'
        this.description = 'Call a custom API endpoint'
        this.baseClasses = [this.type, 'Tool']
        this.credential = {
            label: 'API Credential',
            name: 'credential',
            type: 'credential',
            credentialNames: ['customApiCredential']
        }
        this.inputs = [
            {
                label: 'Endpoint URL',
                name: 'endpoint',
                type: 'string',
                placeholder: 'https://api.example.com/endpoint'
            },
            {
                label: 'HTTP Method',
                name: 'method',
                type: 'options',
                options: [
                    { label: 'GET', name: 'GET' },
                    { label: 'POST', name: 'POST' },
                    { label: 'PUT', name: 'PUT' }
                ],
                default: 'GET'
            },
            {
                label: 'Description',
                name: 'description',
                type: 'string',
                rows: 4,
                description: 'Description of what this API does',
                placeholder: 'Fetches user data from the API'
            }
        ]
    }

    async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
        const endpoint = nodeData.inputs?.endpoint as string
        const method = nodeData.inputs?.method as string
        const description = nodeData.inputs?.description as string

        // Get credentials
        const credentialData = await getCredentialData(nodeData.credential ?? '', options)
        const apiKey = getCredentialParam('apiKey', credentialData, nodeData)

        // Create the tool
        const tool = new DynamicStructuredTool({
            name: 'custom_api',
            description: description || 'Calls a custom API endpoint',
            schema: z.object({
                query: z.string().describe('The query to send to the API')
            }),
            func: async ({ query }) => {
                const response = await fetch(endpoint, {
                    method: method,
                    headers: {
                        'Authorization': `Bearer ${apiKey}`,
                        'Content-Type': 'application/json'
                    },
                    body: method !== 'GET' ? JSON.stringify({ query }) : undefined
                })

                const data = await response.json()
                return JSON.stringify(data)
            }
        })

        return tool
    }
}

module.exports = { nodeClass: CustomAPI_Tools }

Example: Chat Model with Advanced Features

Here’s an example showing async options, credentials, and multiple inputs:
CustomChatModel.ts
import { INode, INodeData, INodeParams, INodeOptionsValue, ICommonObject } from '../../../src/Interface'
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
import { ChatOpenAI, ChatOpenAIFields } from '@langchain/openai'
import { BaseCache } from '@langchain/core/caches'

class CustomChatModel implements INode {
    label: string
    name: string
    version: number
    type: string
    icon: string
    category: string
    description: string
    baseClasses: string[]
    credential: INodeParams
    inputs: INodeParams[]

    constructor() {
        this.label = 'Custom Chat Model'
        this.name = 'customChatModel'
        this.version = 1.0
        this.type = 'CustomChatModel'
        this.icon = 'chat.svg'
        this.category = 'Chat Models'
        this.description = 'Custom implementation of a chat model'
        this.baseClasses = [this.type, ...getBaseClasses(ChatOpenAI)]
        this.credential = {
            label: 'Connect Credential',
            name: 'credential',
            type: 'credential',
            credentialNames: ['customApiKey']
        }
        this.inputs = [
            {
                label: 'Cache',
                name: 'cache',
                type: 'BaseCache',
                optional: true
            },
            {
                label: 'Model Name',
                name: 'modelName',
                type: 'asyncOptions',
                loadMethod: 'listModels',
                default: 'default-model'
            },
            {
                label: 'Temperature',
                name: 'temperature',
                type: 'number',
                step: 0.1,
                default: 0.9,
                optional: true
            },
            {
                label: 'Max Tokens',
                name: 'maxTokens',
                type: 'number',
                step: 1,
                optional: true,
                additionalParams: true
            },
            {
                label: 'Streaming',
                name: 'streaming',
                type: 'boolean',
                default: true,
                optional: true,
                additionalParams: true
            }
        ]
    }

    loadMethods = {
        async listModels(): Promise<INodeOptionsValue[]> {
            // Fetch available models from API
            return [
                { label: 'Model 1', name: 'model-1' },
                { label: 'Model 2', name: 'model-2' },
                { label: 'Model 3', name: 'model-3' }
            ]
        }
    }

    async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
        const temperature = nodeData.inputs?.temperature as string
        const modelName = nodeData.inputs?.modelName as string
        const maxTokens = nodeData.inputs?.maxTokens as string
        const streaming = nodeData.inputs?.streaming as boolean
        const cache = nodeData.inputs?.cache as BaseCache

        // Get credentials
        const credentialData = await getCredentialData(nodeData.credential ?? '', options)
        const apiKey = getCredentialParam('apiKey', credentialData, nodeData)

        const obj: ChatOpenAIFields = {
            temperature: parseFloat(temperature),
            modelName,
            openAIApiKey: apiKey,
            streaming: streaming ?? true
        }

        if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10)
        if (cache) obj.cache = cache

        const model = new ChatOpenAI(obj)
        return model
    }
}

module.exports = { nodeClass: CustomChatModel }

Async Options

Use asyncOptions to load options dynamically:
this.inputs = [
    {
        label: 'Model Name',
        name: 'modelName',
        type: 'asyncOptions',
        loadMethod: 'listModels'
    }
]

loadMethods = {
    async listModels(): Promise<INodeOptionsValue[]> {
        const response = await fetch('https://api.example.com/models')
        const data = await response.json()
        return data.models.map(model => ({
            label: model.name,
            name: model.id
        }))
    }
}

Credentials

If your node requires credentials, define them:
this.credential = {
    label: 'Connect Credential',
    name: 'credential',
    type: 'credential',
    credentialNames: ['openAIApi', 'customApi']
}

// In init method
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const apiKey = getCredentialParam('apiKey', credentialData, nodeData)

Best Practices

Follow these guidelines for high-quality nodes:
  1. Clear naming - Use descriptive names for labels and properties
  2. Proper validation - Validate inputs before processing
  3. Error handling - Catch and handle errors gracefully
  4. Documentation - Add clear descriptions for all inputs
  5. Type safety - Use TypeScript types properly
  6. Versioning - Increment version when making breaking changes

Testing Your Node

1

Build the components package

pnpm build
2

Start Flowise

pnpm start
3

Test in the UI

  1. Open http://localhost:3000
  2. Create a new chatflow
  3. Find your node in the left sidebar under the specified category
  4. Drag it onto the canvas and test its functionality

Adding Icons

Place custom icons in packages/server/src/public/:
this.icon = 'myicon.svg'
Use SVG format for best results.

Debugging

Enable debug mode to see console logs:
DEBUG=true pnpm start
Add logging in your node:
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
    console.log('Node initialization:', nodeData.inputs)
    // Your code here
}

Next Steps

  • Review existing nodes in packages/concepts/nodes-and-edges/nodes/ for more examples
  • Read the contributing guidelines before submitting
  • Learn about the architecture of Flowise

Build docs developers (and LLMs) love