Back to Langflow

Create custom Python components

docs/versioned_docs/version-1.8.0/Components/components-custom-components.mdx

1.10.0.dev2022.9 KB
Original Source

import Icon from "@site/src/components/icon"; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import PartialBasicComponentStructure from '../_partial-basic-component-structure.mdx';

Create your own custom components to add any functionality you need to Langflow, from API integrations to data processing.

In Langflow's node-based environment, each node is a "component" that performs discrete functions. Custom components in Langflow are built upon:

  • The Python class that inherits from Component.
  • Class-level attributes that identify and describe the component.
  • Input and output lists that determine data flow.
  • Methods that define the component's behavior and logic.
  • Internal variables for Error handling and logging

Use the Custom component quickstart to add an example component to Langflow, and then use the reference guide that follows for more advanced component customization.

Custom component quickstart {#quickstart}

Create a custom DataFrameProcessor component by creating a Python file, saving it in the correct folder, including an __init__.py file, and loading it into Langflow.

Create a Python file

<PartialBasicComponentStructure />

Save the custom component {#custom-component-path}

Save the custom component in the Langflow directory where the UI will discover and load it.

By default, Langflow looks for custom components in the src/lfx/src/lfx/components directory.

When saving components in the default directory, components must be organized in a specific directory structure to be properly loaded and displayed in the visual editor.

Components must be placed inside category folders, not directly in the base directory.

The category folder name determines where the component appears in the Langflow <Icon name="Component" aria-hidden="true" /> Core components menu. For example, to add the example DataFrameProcessor component to the Data category, place it in the data subfolder:

src/lfx/src/lfx/components/
    └── data/                      # Category folder (determines menu location)
        ├── __init__.py            # Required - makes it a Python package
        └── dataframe_processor.py # Your custom component file

If you're creating custom components in a different location using the LANGFLOW_COMPONENTS_PATH environment variable, components must be similarly organized in a specific directory structure to be displayed in the visual editor.

/your/custom/components/path/    # Base directory set by LANGFLOW_COMPONENTS_PATH
    └── category_name/
        ├── __init__.py
        └── custom_component.py

You can have multiple category folders to organize components into different categories, with multiple components inside each folder:

/app/custom_components/
    ├── data/
    │   ├── __init__.py
    │   ├── custom_component.py
    │   └── dataframe_processor.py
    └── tools/
        ├── __init__.py
        └── custom_tool.py

Create the __init__.py file

Each category directory must contain an __init__.py file for Langflow to properly recognize and load the components. This is a Python package requirement that ensures the directory is treated as a module.

To include the DataFrameProcessor component, create a file named __init__.py in your component's directory with the following content.

python
from .dataframe_processor import DataFrameProcessor

__all__ = ["DataFrameProcessor"]
<details closed> <summary>Lazy load the DataFrameProcessor component</summary>

Alternatively, you can load your component lazily, which is better for performance but a little more complex.

python
from __future__ import annotations

from typing import TYPE_CHECKING, Any

from lfx.components._importing import import_mod

if TYPE_CHECKING:
    from lfx.components.data.dataframe_processor import DataFrameProcessor

_dynamic_imports = {
    "DataFrameProcessor": "dataframe_processor",
}

__all__ = [
    "DataFrameProcessor",
]

def __getattr__(attr_name: str) -> Any:
    """Lazily import data components on attribute access."""
    if attr_name not in _dynamic_imports:
        msg = f"module '{__name__}' has no attribute '{attr_name}'"
        raise AttributeError(msg)
    try:
        result = import_mod(attr_name, _dynamic_imports[attr_name], __spec__.parent)
    except (ModuleNotFoundError, ImportError, AttributeError) as e:
        msg = f"Could not import '{attr_name}' from '{__name__}': {e}"
        raise AttributeError(msg) from e
    globals()[attr_name] = result
    return result

def __dir__() -> list[str]:
    return list(__all__)

For an additional example of lazy loading, see the FAISS component.

</details>

Load your component

Ensure the application builds your component.

  1. To rebuild the backend and frontend, run make install_frontend && make build_frontend && make install_backend && uv run langflow run --port 7860.

  2. Refresh the frontend application. Your new DataFrameProcessor component is available in the <Icon name="Component" aria-hidden="true" /> Core components menu under the Data category in the visual editor.

Docker deployment

When running Langflow in Docker, mount your custom components directory and set the LANGFLOW_COMPONENTS_PATH environment variable in the docker run command to point to the custom components directory.

bash
docker run -d \
  --name langflow \
  -p 7860:7860 \
  -v ./custom_components:/app/custom_components \
  -e LANGFLOW_COMPONENTS_PATH=/app/custom_components \
  langflowai/langflow:latest

Create the same custom components directory structure as the example in Save the custom component.

/app/custom_components/          # LANGFLOW_COMPONENTS_PATH
    └── data/
        ├── __init__.py
        └── dataframe_processor.py

How components execute

Langflow's engine manages:

  1. Instantiation: A component is created and internal structures are initialized.
  2. Assigning Inputs: Values from the visual editor or connections are assigned to component fields.
  3. Validation and Setup: Optional hooks like _pre_run_setup.
  4. Outputs Generation: run() or build_results() triggers output methods.

You can customize execution by overriding these optional hooks in your custom component code.

  • _pre_run_setup() - Used during Validation and Setup. Add this method inside your component class to initialize component state before execution begins:

    python
    class MyComponent(Component):
        # ... your inputs, outputs, and other attributes ...
    
        def _pre_run_setup(self):
            if not hasattr(self, "_initialized"):
                self._initialized = True
                self.iteration = 0
    
  • Override run or _run - Used during Outputs Generation. Add this method inside your component class to customize the main execution logic:

    python
    class MyComponent(Component):
    
        async def_run(self):
            # Custom execution logic here
            # This runs instead of the default output method calls
            pass
    
  • Store data in self.ctx. Use self.ctx in any of your component methods to share data between method calls.

    python
    class MyComponent(Component):
    
        def _pre_run_setup(self):
            # Initialize counter in setup
            self.ctx["processed_items"] = 0
    
        def process_data(self) -> Data:
            # Increment counter during processing
            self.ctx["processed_items"] += 1
            return Data(data={"item": f"processed {self.ctx['processed_items']}"})
    
        def get_summary(self) -> Data:
            # Access counter in different method
            total = self.ctx["processed_items"]
            return Data(data={"summary": f"Processed {total} items total"})
    

Inputs and outputs

Inputs and outputs are class-level configurations that define how data flows through the component, how it appears in the visual editor, and how connections to other components are validated.

Inputs

Inputs are defined in a class-level inputs list. When Langflow loads the component, it uses this list to render component fields and ports in the visual editor. Users or other components provide values or connections to fill these inputs.

An input is usually an instance of a class from lfx.io (such as StrInput, DataInput, or MessageTextInput).

For example, this component has three inputs: a text field (StrInput), a Boolean toggle (BoolInput), and a dropdown selection (DropdownInput).

python
from lfx.io import StrInput, BoolInput, DropdownInput

inputs = [
    StrInput(name="title", display_name="Title"),
    BoolInput(name="enabled", display_name="Enabled", value=True),
    DropdownInput(name="mode", display_name="Mode", options=["Fast", "Safe", "Experimental"], value="Safe")
]

The StrInput creates a single-line text field for entering text. The name="title" parameter means you access this value in your component methods with self.title, while display_name="Title" shows "Title" as the label in the visual editor.

The BoolInput creates a boolean toggle that's enabled by default with value=True. Users can turn this on or off, and you access the current state with self.enabled.

The DropdownInput provides a selection menu with three predefined options: "Fast", "Safe", and "Experimental". The value="Safe" sets "Safe" as the default selection, and you access the user's choice with self.mode.

For a list of all available parameters, see the BaseInputMixin definition in the Langflow codebase.

For a list of all available input types, see the input type definitions in the Langflow codebase.

python
from lfx.io import StrInput, DataInput, MultilineInput, IntInput, BoolInput, DropdownInput, FileInput, CodeInput, ModelInput, HandleInput, Output

Outputs

Outputs are defined in a class-level outputs list. When Langflow renders a component, each output becomes a connector point in the visual editor. When you connect something to an output, Langflow automatically calls the corresponding method and passes the returned object to the next component.

An output is usually an instance of Output from lfx.io.

For example, this component has one output that returns a DataFrame:

python
from lfx.io import Output
from lfx.schema import DataFrame

outputs = [
    Output(
        name="df_out",
        display_name="DataFrame Output",
        method="build_df"
    )
]

def build_df(self) -> DataFrame:
    # Process data and return DataFrame
    df = DataFrame({"col1": [1, 2], "col2": [3, 4]})
    self.status = f"Built DataFrame with {len(df)} rows."
    return df

The Output creates a connector point in the visual editor labeled DataFrame Output. The name="df_out" parameter identifies this output, while display_name="DataFrame Output" shows the label in the UI. The method="build_df" parameter tells Langflow to call the build_df method when this output is connected to another component.

The build_df method processes data and returns a DataFrame. The -> DataFrame type annotation helps Langflow validate connections and provides color-coding in the visual editor. You can also set self.status to show progress messages in the UI.

For a complete list of all available parameters, see the Output class definition in the Langflow codebase. Common parameters include:

Additional return types:

  • Message: Structured chat messages
  • Data: Flexible object with .data and optional .text
  • DataFrame: Tabular data (pandas DataFrame subclass)
  • Primitive types: str, int, bool, not recommended for type consistency

Associated methods

Each output is linked to a method where the output method name must match the method name. The method typically returns objects like Message, Data, or DataFrame, and can use inputs with self.<input_name>.

For example, the Output defines a connector point called file_contents that will call the read_file method when connected. The read_file method accesses the filename input with self.filename, reads the file content, sets a status message, and returns the content wrapped in a Data object.

python
Output(
    name="file_contents",
    display_name="File Contents",
    method="read_file"
)

def read_file(self) -> Data:
    path = self.filename
    with open(path, "r") as f:
        content = f.read()
    self.status = f"Read {len(content)} chars from {path}"
    return Data(data={"content": content})

Components with multiple outputs

A component can define multiple outputs. Each output can have a different corresponding method.

For example:

python
outputs = [
    Output(display_name="Processed Data", name="processed_data", method="process_data"),
    Output(display_name="Debug Info", name="debug_info", method="provide_debug_info"),
]

By default, components in Langflow that produce multiple outputs only allow one output selection in the visual editor. The component will have only one output port where the user can select the preferred output type.

This behavior is controlled by the group_outputs parameter:

  • group_outputs=False (default): When a component has more than one output and group_outputs is false or not set, the outputs are grouped in the visual editor, and the user must select one.

    Use this option when the component is expected to return only one type of output when used in a flow.

  • group_outputs=True: All outputs are available simultaneously in the visual editor. The component has one output port for each output, and the user can connect zero or more outputs to other components.

    Use this option when the component is expected to return multiple values that are used in parallel by downstream components or processes.

<Tabs> <TabItem value="false" label="False or not set" default>

In this example, the visual editor provides a single output port, and the user can select one of the outputs. Since group_outputs=False is the default behavior, it doesn't need to be explicitly set in the component, as shown in this example.

python
outputs = [
    Output(
        name="structured_output",
        display_name="Structured Output",
        method="build_structured_output",
    ),
    Output(
        name="dataframe_output",
        display_name="DataFrame Output",
        method="build_structured_dataframe",
    ),
]
</TabItem> <TabItem value="true" label="True">

In this example, all outputs are available simultaneously in the visual editor.

python
outputs = [
    Output(
        name="true_result",
        display_name="True",
        method="true_response",
        group_outputs=True,
    ),
    Output(
        name="false_result",
        display_name="False",
        method="false_response",
        group_outputs=True,
    ),
]
</TabItem> </Tabs>

Tool mode

Components that support Tool Mode can be used as standalone components (when not in Tool Mode) or as tools for other components with a Tools input, such as Agent components.

You can allow a custom component to support Tool Mode by setting tool_mode=True:

python
inputs = [
    MessageTextInput(
        name="message",
        display_name="Mensage",
        info="Enter the message that will be processed directly by the tool",
        tool_mode=True,
    ),
]

Typed annotations

In Langflow, typed annotations allow Langflow to visually guide users and maintain flow consistency. Always annotate your output methods with return types like -> Data, -> Message, or -> DataFrame to enable proper visual editor color-coding and validation. Use Data, Message, or DataFrame wrappers instead of returning plain structures for better consistency. Stay consistent with types across your components to make flows predictable and easier to build.

Typed annotations provide color-coding where outputs like -> Data or -> Message get distinct colors, automatic validation that blocks incompatible connections, and improved readability for users to quickly understand data flow between components.

Common return types

<Tabs> <TabItem value="message" label="Message" default>

For chat-style outputs. Connects to any of several Message-compatible inputs.

python
def produce_message(self) -> Message:
    return Message(text="Hello! from typed method!", sender="System")
</TabItem> <TabItem value="data" label="Data">

For structured data like dicts or partial texts. Connects only to DataInput (ports that accept Data).

python
def get_processed_data(self) -> Data:
    processed = {"key1": "value1", "key2": 123}
    return Data(data=processed)
</TabItem> <TabItem value="dataframe" label="DataFrame">

For tabular data. Connects only to DataFrameInput (ports that accept DataFrame).

python
def build_df(self) -> DataFrame:
    pdf = pd.DataFrame({"A": [1, 2], "B": [3, 4]})
    return DataFrame(pdf)
</TabItem> <TabItem value="primitives" label="Primitive Types">

Returning primitives is allowed, but wrapping in Data or Message is recommended for better consistency in the visual editor.

python
def compute_sum(self) -> int:
    return sum(self.numbers)
</TabItem> </Tabs>

Enable dynamic fields

In Langflow, dynamic fields allow inputs to change or appear based on user interactions. You can make an input dynamic by setting dynamic=True. Optionally, setting real_time_refresh=True triggers the update_build_config method to adjust the input's visibility or properties in real time, creating a contextual visual editor experience that only exposes relevant fields based on the user's choices.

In this example, the operator field triggers updates with real_time_refresh=True. The regex_pattern field is initially hidden and controlled with dynamic=True.

python
from lfx.custom import Component
from lfx.io import DropdownInput, StrInput

class RegexRouter(Component):
    display_name = "Regex Router"
    description = "Demonstrates dynamic fields for regex input."

    inputs = [
        DropdownInput(
            name="operator",
            display_name="Operator",
            options=["equals", "contains", "regex"],
            value="equals",
            real_time_refresh=True,
        ),
        StrInput(
            name="regex_pattern",
            display_name="Regex Pattern",
            info="Used if operator='regex'",
            dynamic=True,
            show=False,
        ),
    ]

Show or hide fields based on user selections

When a user changes a field with real_time_refresh=True, Langflow calls your update_build_config method.

This method lets you show, hide, or modify other fields based on what the user selected.

This example shows the regex_pattern field only when the user selects "regex" from the operator dropdown.

python
def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None) -> dict:
    if field_name == "operator":
        if field_value == "regex":
            build_config["regex_pattern"]["show"] = True
        else:
            build_config["regex_pattern"]["show"] = False
    return build_config

You can modify additional field properties in update_build_config other than just show and hide.

  • required: Make fields required or optional dynamically

    python
    if field_value == "regex":
        build_config["regex_pattern"]["required"] = True
    else:
        build_config["regex_pattern"]["required"] = False
    
  • advanced: Move fields to the "Advanced" section

    python
    if field_value == "experimental":
        build_config["regex_pattern"]["advanced"] = False  # Show in main section
    else:
        build_config["regex_pattern"]["advanced"] = True   # Hide in advanced
    
  • options: Change dropdown options based on other selections

    python
    if field_value == "regex":
        build_config["operator"]["options"] = ["regex", "contains", "starts_with"]
    else:
        build_config["operator"]["options"] = ["equals", "contains", "not_equals"]
    

Error handling and logging

You can raise standard Python exceptions such as ValueError or specialized exceptions like ToolException when validation fails. Langflow automatically catches these and displays appropriate error messages in the visual editor, helping users quickly identify what went wrong.

python
def compute_result(self) -> str:
    if not self.user_input:
        raise ValueError("No input provided.")
    # ...

Alternatively, instead of stopping a flow abruptly, you can return a Data object containing an "error" field. This approach allows the flow to continue operating and enables downstream components to detect and handle the error gracefully.

python
def run_model(self) -> Data:
    try:
        # ...
    except Exception as e:
        return Data(data={"error": str(e)})

Langflow provides several tools to help you debug and manage component execution. You can use self.status to display short messages about execution results directly in the visual editor, making troubleshooting easier for users.

python
def parse_data(self) -> Data:
# ...
self.status = f"Parsed {len(rows)} rows successfully."
return Data(data={"rows": rows})

You can halt individual output paths when certain conditions fail using self.stop(), without stopping other outputs from the same component.

This example stops the output if the user input is empty, preventing the component from processing invalid data.

python
def some_output(self) -> Data:
if not self.user_input or len(self.user_input.strip()) == 0:
    self.stop("some_output")
    return Data(data={"error": "Empty input provided"})

You can log key execution details inside components using self.log(). These logs are stored as structured data and displayed in the "Logs" or "Events" section of the component's detail view, and can be accessed later through the Logs button in the visual editor or exported files.

Component logs are distinct from Langflow's main application logging system. self.log() creates component-specific logs that appear in the UI, while Langflow's main logging system uses structlog for application-level logging that outputs to langflow.log files. For more information, see Logs.

This example logs a message when the component starts processing a file.

python
def process_file(self, file_path: str):
self.log(f"Processing file {file_path}")

Contribute custom components to Langflow

To contribute your custom component to the Langflow project, see Contribute components.