Building Custom LangFlow Components
If you have spent any real time in LangFlow you already know the built-in nodes only get you so far. The moment your pipeline needs a proprietary API call, a specific chunking strategy, or a validation step that does not exist as a drag-and-drop block, you need langflow custom components. This guide walks through the component architecture, the required class structure, inputs and outputs, and how to package a component so it shows up cleanly in the canvas sidebar like any first-party node.
LangFlow is built on top of LangChain concepts but exposes everything as a visual graph. Every node you drag onto the canvas is backed by a Python class. Once you understand that class contract, writing your own component is no different from writing a small, well-scoped Python module. The rest is wiring: inputs, outputs, and a build (or run) method that does the work.
Why write langflow custom components instead of using existing nodes
The stock component library covers the common cases: LLM calls, vector store retrieval, prompt templates, basic tool use. It does not cover:
- Internal REST APIs your team owns, with custom auth headers or retry logic
- Domain-specific preprocessing, like normalizing medical codes or parsing a proprietary log format
- Business rules that need to run inline in the flow, such as PII redaction before a document hits an LLM
- Wrapping a Python library that has no LangChain integration yet
You could technically stuff all of this into a single "Python Code" node using LangFlow's inline code component, and for a one-off experiment that is fine. But inline code blocks do not version well, cannot be unit tested outside the canvas, and cannot be shared across flows or teammates without copy-pasting. A proper custom component lives in its own file, gets imported like a normal module, and shows up in the component sidebar with a real name, icon, and documented inputs. That is the difference between a hack and infrastructure.
The anatomy of a LangFlow component
Every custom component subclasses Component from langflow.custom. At minimum you need four things: a display name, an icon, a list of typed inputs, and a list of typed outputs backed by methods that produce them.
from langflow.custom import Component
from langflow.io import MessageTextInput, Output
from langflow.schema import Data
class WordCountComponent(Component):
display_name = "Word Count"
description = "Counts words in the input text and returns the total."
icon = "type"
name = "WordCountComponent"
inputs = [
MessageTextInput(
name="input_text",
display_name="Text",
info="The text to count words in.",
required=True,
),
]
outputs = [
Output(display_name="Word Count", name="word_count", method="count_words"),
]
def count_words(self) -> Data:
text = self.input_text or ""
count = len(text.split())
result = Data(data={"word_count": count, "text": text})
self.status = f"Counted {count} words"
return resultA few things to notice here because they trip up almost everyone the first time:
The name class attribute has to be unique within your component library. LangFlow uses it internally to identify the node type when a flow is saved and reloaded. If two components share a name, you will see flows silently pick the wrong one after a restart.
The outputs list maps a named output to a method on the class, not the other way around. LangFlow inspects that method at load time to build the output socket on the node. The method name in method="count_words" must exactly match a method you define below.
self.status is not cosmetic. It is what shows up in the small text under the node when you hover over it after a run, and it is invaluable for debugging a chain of five or six custom nodes without opening logs.
Input types you will actually use
LangFlow ships a set of typed input classes in langflow.io, and using the right one matters because it controls both validation and the widget rendered in the UI.
from langflow.io import (
MessageTextInput,
StrInput,
IntInput,
BoolInput,
DropdownInput,
SecretStrInput,
HandleInput,
)MessageTextInput accepts either a plain string or a Message object coming from an upstream node, which makes it the right default for anything that might be chained after an LLM or another component. StrInput is for plain configuration strings that will never be a Message, like a model name.
SecretStrInput masks the field in the UI and is the one you want for API keys. Never use StrInput for credentials. Even in a self-hosted LangFlow instance, SecretStrInput keeps the value out of flow exports and screen shares, which matters the moment someone pastes a flow JSON into a support ticket.
DropdownInput takes an options list and renders a select box, which is the right call whenever the valid values are a known finite set:
DropdownInput(
name="strategy",
display_name="Chunking Strategy",
options=["fixed", "sentence", "semantic"],
value="fixed",
)HandleInput is the one that confuses people coming from simpler node editors. It does not render a text field at all, it renders a connection socket that only accepts output from another component of a matching type. Use it when your component needs to receive, say, a LanguageModel object or a VectorStore instance from an upstream node rather than a plain value.
HandleInput(
name="llm",
display_name="Language Model",
input_types=["LanguageModel"],
)Get input_types wrong (a typo, or a type that no upstream node actually emits) and the socket will simply refuse every connection you try to drag into it, with no error message, which is the single most common reason a langflow custom components build feels stuck for beginners.
Multiple outputs and conditional execution
A component is not limited to one output. This is useful for branching logic, like a validator that routes to a "pass" path or a "fail" path:
from langflow.io import Output
from langflow.schema import Data
class ValidationRouter(Component):
display_name = "Validation Router"
icon = "git-branch"
name = "ValidationRouter"
inputs = [
MessageTextInput(name="value", display_name="Value", required=True),
IntInput(name="min_length", display_name="Minimum Length", value=10),
]
outputs = [
Output(display_name="Valid", name="valid_output", method="check_valid"),
Output(display_name="Invalid", name="invalid_output", method="check_invalid"),
]
def _is_valid(self) -> bool:
return len(self.value or "") >= self.min_length
def check_valid(self) -> Data:
if not self._is_valid():
self.stop("valid_output")
return Data(data={})
return Data(data={"value": self.value, "valid": True})
def check_invalid(self) -> Data:
if self._is_valid():
self.stop("invalid_output")
return Data(data={})
return Data(data={"value": self.value, "valid": False})self.stop(output_name) is the mechanism that prevents a branch from firing downstream. Without it, both outputs would produce data and both downstream paths would execute, which defeats the point of a router. This pattern, one method per output plus explicit stop calls, is how you build if/else logic natively into a flow instead of relying on a separate conditional node glued in with wires.
Testing a component outside the canvas
The biggest productivity win of writing real Python classes instead of inline code is that you can test the class directly, without booting the LangFlow server at all.
def test_word_count_component():
component = WordCountComponent()
component.input_text = "the quick brown fox jumps"
result = component.count_words()
assert result.data["word_count"] == 5
def test_validation_router_valid_path():
component = ValidationRouter()
component.value = "a sufficiently long string"
component.min_length = 10
result = component.check_valid()
assert result.data["valid"] is TrueRun these with plain pytest before you ever drag the node onto a canvas:
pytest tests/test_components.py -vCatching a bug in a unit test takes seconds. Catching the same bug by running a five-node flow through the LangFlow UI, waiting for the LLM call at node three to finish, and then discovering node four is broken takes minutes, every single time you iterate. If you are building more than two or three custom nodes, write the tests first.
Packaging and registering your component library
LangFlow discovers custom components in two ways: a components directory pointed at by an environment variable or CLI flag, or a bundled Python package installed into the same environment.
For local development, the directory approach is fastest. Set the components path when starting LangFlow:
export LANGFLOW_COMPONENTS_PATH=/path/to/my_components
langflow runInside my_components, organize by category, since LangFlow uses the folder structure to group nodes in the sidebar:
my_components/
text_processing/
word_count.py
validation_router.py
integrations/
internal_api_client.pyEach .py file should contain exactly one component class. LangFlow scans the directory on startup and on demand when you hit refresh in the UI, so during development you can edit a file, hit the refresh icon in the component sidebar, and see your changes without restarting the whole server.
For anything you intend to share across a team or ship to production, wrap the components directory as an installable package with a pyproject.toml, publish it to your internal package index, and have deployment install it alongside LangFlow itself. That turns "custom component" into a versioned dependency instead of a folder someone has to remember to copy.
Handling errors inside a component
A custom component that throws an unhandled exception takes down the whole flow run with a stack trace the end user of your app will never understand. Wrap risky operations and surface failures through LangFlow's own error handling instead:
from langflow.custom import Component
from langflow.io import MessageTextInput, Output
from langflow.schema import Data
class InternalApiClient(Component):
display_name = "Internal API Client"
icon = "server"
name = "InternalApiClient"
inputs = [
MessageTextInput(name="query", display_name="Query", required=True),
]
outputs = [
Output(display_name="Response", name="response", method="call_api"),
]
def call_api(self) -> Data:
import requests
try:
resp = requests.post(
"https://internal.example.com/api/search",
json={"query": self.query},
timeout=10,
)
resp.raise_for_status()
except requests.RequestException as exc:
self.status = f"API call failed: {exc}"
return Data(data={"error": str(exc), "success": False})
payload = resp.json()
self.status = "API call succeeded"
return Data(data={"result": payload, "success": True})Returning a Data object with an error key, rather than letting the exception propagate, means downstream components can check for success and branch accordingly instead of the entire flow crashing on a transient network blip.
Common mistakes when building langflow custom components
A short list, drawn from patterns that show up over and over in component code:
- Forgetting
required=Trueon inputs that the method assumes are present, which produces confusingNoneTypeerrors deep insidebuild - Returning a raw Python dict or string instead of the expected
DataorMessageschema type, which breaks type-checking on the connecting socket - Mutating class-level attributes instead of instance attributes, which causes state to leak between separate runs of the same flow
- Doing expensive setup, like loading a model or opening a database connection, inside the output method instead of caching it, so every single output method call redoes the work
- Skipping the
iconfield, which just makes the node harder to spot at a glance in a busy canvas, small thing but it adds up across a real component library
FAQ
Do langflow custom components need to be written in Python? Yes. LangFlow's backend and its component system are Python, so custom nodes are always Python classes. The visual canvas is a JavaScript frontend, but it only ever talks to Python components through the defined input and output schema. There is no supported way to write a component in another language.
Can a custom component call an async function? Yes, define the output method as async def instead of def, and LangFlow will await it during flow execution. This matters if your component calls an async HTTP client or an LLM SDK that exposes an async interface, since blocking a sync call inside an async flow run can serialize work that should be running concurrently.
How do I add a custom component without restarting the server? Point LANGFLOW_COMPONENTS_PATH at your directory, save your .py file, then click the refresh icon in the component sidebar inside the LangFlow UI. It rescans the directory and picks up new or changed files. A full server restart is only needed if you change dependencies the component imports.
What is the difference between a custom component and the inline Python code node? The inline code node lets you paste a component class directly into a text box inside a flow, which is convenient for quick experiments but is not version controlled, not unit testable outside the UI, and not reusable across flows without copying the text. A file-based custom component is a real module you can put under source control, test with pytest, and reference from multiple flows.
Can one component have both required and optional inputs? Yes. Set required=True only on the inputs your method cannot run without, and leave the rest with a sensible value default. LangFlow renders required inputs with a visual marker in the UI and will block a flow from running if one is left empty and unconnected.
How do I debug a component that is not appearing in the sidebar? Check three things in order: the file is inside the directory pointed to by LANGFLOW_COMPONENTS_PATH, the class actually subclasses Component and has a unique name attribute, and the file has no import-time exception. A component that raises on import is silently skipped by the scanner rather than shown as broken, so check the LangFlow server logs on startup for import errors if a node you wrote does not show up.
BootcampA 30-day guided bootcamp: build, harden and ship a production autonomous agent from scratch.
AI AgentsUnderstand how AI agents really work: the loop, the tools, the memory, and why most agent projects fail.
Related reading