> ## Documentation Index
> Fetch the complete documentation index at: https://docs.llmcontrols.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Create custom Python components

Custom components extend LLM Controls' functionality through Python classes that inherit from `Component`. This enables integration of new features, data manipulation, external services, and specialized tools.

In LLM Controls' node-based environment, each node is a "component" that performs discrete functions. Custom components are Python classes that define:

* **Inputs**: Data or parameters your component requires.
* **Outputs**: Data your component provides to downstream nodes.
* **Logic**: How you process inputs to produce outputs.

The benefits of creating custom components include unlimited extensibility, reusability, automatic UI field generation based on inputs, and type-safe connections between nodes.

Create custom components for performing specialized tasks, calling APIs, or adding advanced logic.

Custom components in LLM Controls 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.
* Internal variables for logging and advanced logic.

## **Class-level attributes**[**​**](https://docs.langflow.org/components-custom-components#class-level-attributes)

Define these attributes to control a custom component's appearance and behavior:

* **display\_name**: A user-friendly label in the node header.
* **description**: A brief summary shown in tooltips.
* **icon**: A visual identifier from LLM Controls' icon library.
* **name**: A unique internal identifier.
* **documentation**: An optional link to external docs.

<Tip>
  **ICON USAGE**

  LLM Controls uses [<u>Lucide</u>](https://lucide.dev/icons) for icons. To assign an icon to your component, set the icon attribute to the name of a Lucide icon as a string, such as `icon = "file-text"`. LLM Controls renders icons from the Lucide library automatically.
</Tip>

### **Structure of a custom component**

A **LLM Controls custom component** goes beyond a simple class with inputs and outputs. It includes an internal structure with optional lifecycle steps, output generation, front-end interaction, and logic organization.

A basic component:

* Inherits from `llmc.custom.Component`.
* Declares metadata like `display_name`, `description`, `icon`, and more.
* Defines `inputs` and `outputs` lists.
* Implements methods matching output specifications.

### A minimal custom component skeleton contains the following:

### **1. Internal Lifecycle and Execution Flow**

LLM Controls' engine manages:

* **Instantiation**: A component is created, and internal structures are initialized.
* **Assigning Inputs**: Values from the UI or connections are assigned to component fields.
* **Validation and Setup**: Optional hooks like `_pre_run_setup`.
* **Outputs Generation**: `run()` or `build_results()` triggers output methods.

**Optional Hooks**:

* `initialize_data` or `_pre_run_setup` can run setup logic before the component's main execution.
* `__call__`, `run()`, or `_run()` can be overridden to customize how the component is called or to define custom execution logic.

### **2. Inputs and outputs**[**​**](https://docs.langflow.org/components-custom-components#inputs-and-outputs)

Custom component inputs are defined with properties like:

* `name`, `display_name`
* Optional: `info`, `value`, `advanced`, `is_list`, `tool_mode`, `real_time_refresh`

For example:

* `StrInput`: simple text input.
* `DropdownInput`: selectable options.
* `HandleInput`: specialized connections.

Custom component `Output` properties define:

* `name`, `display_name`, `method`
* Optional: `info`

For more information, see [Custom component inputs and outputs](https://docs.llmcontrols.ai/Components/CreatecustomPythoncomponents#custom-component-inputs-and-outputs).

### **3. Associated Methods**[**​**](https://docs.langflow.org/components-custom-components#associated-methods)

Each output is linked to a method:

* The output method name must match the method name.
* The method typically returns objects like Message, Data, or DataFrame.
* The method can use inputs with `self.<input_name>`.

**For example:**

```text theme={null}

from llmc.custom import Component
from llmc.io import MessageTextInput, Output
from llmc.schema.message import Message
class ExampleComponent(Component):
    inputs = [
        MessageTextInput(name="input_text", display_name="Input Text"),
    ]
    
    outputs = [
        Output(display_name="Result", name="result", method="process_text"),
    ]
    
    def process_text(self) -> Message:
        # Method name matches the output's method attribute
        processed = self.input_text.upper()
        return Message(text=processed)
```

### **Components with multiple outputs**

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

**For example:**

```text theme={null}

from llmc.custom import Component
from llmc.io import MessageTextInput, IntInput, Output
from llmc.schema.message import Message
class MultipleOutputsComponent(Component):
    inputs = [
        MessageTextInput(name="input", display_name="Input"),
        IntInput(name="number", display_name="Number"),
    ]
    
    outputs = [
        Output(display_name="Text Output", name="text_output", method="text_output"),
        Output(display_name="Number Output", name="number_output", method="number_output"),
    ]
    
    def text_output(self) -> str:
        return f"Input text: {self.input}"
    
    def number_output(self) -> int:
        return self.number * 2
```

### **Common internal patterns**[**​**](https://docs.langflow.org/components-custom-components#common-internal-patterns)

#### `_pre_run_setup()`[**​**](https://docs.langflow.org/components-custom-components#_pre_run_setup)

To initialize a custom component with counters or state variables :

```text theme={null}

class ConditionalRouterComponent(Component):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__iteration_updated = False
    
    def _pre_run_setup(self):
        # Reset state before each run
        self.__iteration_updated = False
```

#### Override `run`or  `_run`

You can override `async def _run(self): ...` to define custom execution logic, although the default behavior from the base class usually covers most cases.

```text theme={null}

async def _run(self):
    # Custom execution logic here
    result = await self.custom_processing()
    return result
```

#### Store data in `self.ctx`

Use `self.ctx` as a shared storage for data or counters across the component's execution flow:

```text theme={null}

from llmc.custom import Component
from llmc.io import MessageTextInput, Output
from llmc.schema.message import Message
class ComponentWithContext(Component):
    inputs = [
        MessageTextInput(name="input_text", display_name="Input Text"),
    ]
    
    outputs = [
        Output(display_name="Result", name="result", method="process_with_context"),
    ]
    
    def process_with_context(self) -> Message:
        # Initialize context if needed
        if "counter" not in self.ctx:
            self.update_ctx({"counter": 0})
        
        # Access context data
        counter = self.ctx.get("counter", 0)
        
        # Update context
        self.update_ctx({"counter": counter + 1, "last_input": self.input_text})
        
        # Use context in processing
        result = f"Processed {counter + 1} times: {self.input_text}"
        return Message(text=result)
```

### **Alternative**

Using add\_to\_ctx()You can also add individual values to the context:

```text theme={null}

# Add a single value to context
self.add_to_ctx("user_preference", "dark_mode")
# Access it later
preference = self.ctx.get("user_preference", "light_mode")
```

<Note>
  The context (self.ctx) is shared across all components in the same flow execution, allowing you to pass data between components without explicit connections.
</Note>

## **Directory structure requirements**

By default, LLM Controls looks for custom components in the `llmc/components` directory.

If you're creating custom components in a different location using the  LLM Controls COMPONENTS PATH environment variable, components must be organized in a specific directory structure to be properly loaded and displayed in the UI:

Components must be placed inside **category folders**, not directly in the base directory. The category folder name determines where the component appears in the UI menu.

For example, to add a component to the **Helpers** menu, place it in a `helpers` subfolder:

```text theme={null}

my_custom_components/
└── helpers/
    └── my_helper_component.py
```

You can have **multiple category folders** to organize components into different menus:

```text theme={null}


my_custom_components/
├── helpers/
│   └── my_helper_component.py
├── tools/
│   ├── my_custom_tool.py
│   └── another_tool.py
├── models/
│   └── my_custom_model.py
└── processing/
    └── my_processor.py
```

This folder structure is required for LLM Controls to properly discover and load your custom components. Components placed directly in the base directory will not be loaded.

## **Custom component inputs and outputs**

Inputs and outputs define how data flows through the component, how it appears in the UI, and how connections to other components are validated.

### **Inputs**[**​**](https://docs.langflow.org/components-custom-components#inputs)

Inputs are defined in a class-level `inputs` list. When LLM Controls loads the component, it uses this list to render fields and [ports](https://docs.llmcontrols.ai/Components/componentoverview#component-ports) in the UI. Users or other components provide values or connections to fill these inputs.

An input is usually an instance of a class from `llmc.io` (such as `StrInput`, `DataInput`, or `MessageTextInput`). The most common constructor parameters are:

* `name`: The internal variable name, accessed via `self.<name>`.
* `display_name`: The label shown to users in the UI.
* `info` *(optional)*: A tooltip or short description.
* `value` *(optional)*: The default value.
* `advanced` *(optional)*: If `True`, moves the field into the "Advanced" section.
* `required` *(optional)*: If `True`, forces the user to provide a value.
* `is_list` *(optional)*: If `True`, allows multiple values.
* `input_types` *(optional)*: Restricts allowed connection types (e.g., `["Data"]`, `["LanguageModel"]`).

Here are the most commonly used input classes and their typical usage.

**Text Inputs**: For simple text entries.

* `StrInput` creates a single-line text field.
* `MultilineInput` creates a multi-line text area.

**Numeric and Boolean Inputs**: Ensures users can only enter valid numeric or boolean data.

* `BoolInput`, `IntInput`, and `FloatInput` provide fields for boolean, integer, and float values, ensuring type consistency.

**Dropdowns**: For selecting from predefined options, useful for modes or levels.

* `DropdownInput`

**Secrets**: A specialized input for sensitive data, ensuring input is hidden in the UI.

* `SecretStrInput` for API keys and passwords.

**Specialized Data Inputs**: Ensures type-checking and color-coded connections in the UI.

* `DataInput` expects a `Data` object (typically with `.data` and optional `.text`).
* `MessageInput` expects a `Message` object, used in chat or agent-based flows.
* `MessageTextInput` simplifies access to the `.text` field of a `Message`.

**Handle-Based Inputs**: Used to connect outputs of specific types, ensuring correct pipeline connections.

* `HandleInput`

**File Uploads**: Allows users to upload files directly through the UI or receive file paths from other components.

* `FileInput`

**Lists**: Set `is_list=True` to accept multiple values, ideal for batch or grouped operations.

This example defines three inputs: a text field (`StrInput`), a boolean toggle (`BoolInput`), and a dropdown selection (`DropdownInput`).

### **Outputs**[**​**](https://docs.langflow.org/components-custom-components#outputs)

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

An output is usually an instance of `Output` from `llmc.io`, with common parameters:

* `name`: The internal variable name.
* `display_name`: The label shown in the UI.
* `method`: The name of the method called to produce the output.
* `info` *(optional)*: Help text shown on hover.

The method must exist in the class, and it is recommended to annotate its return type for better type checking. You can also set a `self.status` message inside the method to show progress or logs.

**Common Return Types**:

* `Message`: Structured chat messages.
* `Data`: Flexible object with `.data` and optional `.text`.
* `DataFrame`: Pandas-based tables (`llmc.schema.DataFrame`).
* **Primitive types**: `str`, `int`, `bool` (not recommended if you need type/color consistency).

```text theme={null}

from llmc.custom import Component
from llmc.io import DataInput, Output
from llmc.schema import Data, DataFrame

class DataToDataFrameComponent(Component):
    display_name = "Data → DataFrame"
    
    inputs = [
        DataInput(
            name="data_list",
            display_name="Data or Data List",
            info="One or multiple Data objects to transform into a DataFrame.",
            is_list=True,
        ),
    ]

    outputs = [
        Output(
            display_name="DataFrame",
            name="dataframe",
            method="build_dataframe",
            info="A DataFrame built from each Data object's fields plus a 'text' column.",
        ),
    ]

    def build_dataframe(self) -> DataFrame:
        """Builds a DataFrame from Data objects by combining their fields."""
        data_input = self.data_list
        
        if not isinstance(data_input, list):
            data_input = [data_input]
        
        rows = []
        for item in data_input:
            row_dict = dict(item.data) if item.data else {}
            text_val = item.get_text()
            if text_val:
                row_dict["text"] = text_val
            rows.append(row_dict)
        
        return DataFrame(rows)
```

In this example, the `DataToDataFrame` component defines its output using the outputs list. The `df_out` output is linked to the `build_df` method, so when connected in the UI, LLM Controls calls this method and passes its returned DataFrame to the next node. This demonstrates how each output maps to a method that generates the actual output data.

### **Tool mode**[**​**](https://docs.langflow.org/components-custom-components#tool-mode)

You can configure a Custom Component to work as a **Tool** by setting the parameter `tool_mode=True`. This allows the component to be used in LLM Controls' Tool Mode workflows, such as by Agent components.

LLM Controls currently supports the following input types for Tool Mode:

* `DataInput`
* `DataFrameInput`
* `PromptInput`
* `MessageTextInput`
* `MultilineInput`
* `DropdownInput`

## **Typed annotations**[**​**](https://docs.langflow.org/components-custom-components#typed-annotations)

In LLM Controls, **typed annotations** allow LLM Controls to visually guide users and maintain flow consistency.

Typed annotations provide:

* **Color-coding**: Outputs like `-> Data` or `-> Message` get distinct colors.
* **Validation**: LLM Controls blocks incompatible connections automatically.
* **Readability**: Developers can quickly understand data flow.
* **Development tools**: Better code suggestions and error checking in your code editor.

### **Common Return Types**[**​**](https://docs.langflow.org/components-custom-components#common-return-types)

`Message`

For chat-style outputs.

In the UI, connects only to Message-compatible inputs.

`Data`

For structured data like dicts or partial texts.

In the UI, connects only with DataInput.

`DataFrame`

For tabular data

In the UI, connects only to DataFrameInput.

**Primitive Types (**`str`******`, int, bool)`******

Returning primitives is allowed but wrapping in Data or Message is recommended for better UI consistency.

### **Tips for typed annotations**[**​**](https://docs.langflow.org/components-custom-components#tips-for-typed-annotations)

When using typed annotations, consider the following best practices:

* **Always Annotate Outputs**: Specify return types like `-> Data`, `-> Message`, or `-> DataFrame` to enable proper UI color-coding and validation.
* **Wrap Raw Data**: Use `Data`, `Message`, or `DataFrame` wrappers instead of returning plain structures.
* **Use Primitives Carefully**: Direct `str` or `int` returns are fine for simple flows, but wrapping improves flexibility.
* **Annotate Helpers Too**: Even if internal, typing improves maintainability and clarity.
* **Handle Edge Cases**: Prefer returning structured `Data` with error fields when needed.
* **Stay Consistent**: Use the same types across your components to make flows predictable and easier to build.

## **Enable dynamic fields**[**​**](https://docs.langflow.org/components-custom-components#enable-dynamic-fields)

In **LLM Controls**, 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 UI that only displays relevant fields based on the user's choices.

```text theme={null}

from llmc.custom import Component
from llmc.io import DropdownInput, MessageTextInput, Output
from llmc.schema.message import Message

class ConditionalRouterComponent(Component):
    display_name = "If-Else"
    
    inputs = [
        MessageTextInput(
            name="input_text",
            display_name="Text Input",
            info="The primary text input for the operation.",
            required=True,
        ),
        MessageTextInput(
            name="match_text",
            display_name="Match Text",
            info="The text input to compare against.",
            required=True,
        ),
        DropdownInput(
            name="operator",
            display_name="Operator",
            options=["equals", "not equals", "contains", "regex"],
            info="The operator to apply for comparing the texts.",
            value="equals",
            real_time_refresh=True,  # Triggers update_build_config
        ),
        MessageTextInput(
            name="regex_pattern",
            display_name="Regex Pattern",
            info="The regex pattern to match (only shown when operator is 'regex').",
            dynamic=True,  # Field visibility controlled dynamically
        ),
    ]

    outputs = [
        Output(display_name="Result", name="result", method="process"),
    ]

    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":
                # Show regex_pattern field when operator is "regex"
                build_config["regex_pattern"]["show"] = True
            else:
                # Hide regex_pattern field for other operators
                build_config["regex_pattern"]["show"] = False
        return build_config

    def process(self) -> Message:
        # Component logic here
        return Message(text="Result")
```

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

### Implement `update_build_config`[**​**](https://docs.langflow.org/components-custom-components#implement-update_build_config)

When a field with `real_time_refresh=True` is modified, LLM Controls calls the `update_build_config` method, passing the updated field name, value, and the component's configuration to dynamically adjust the visibility or properties of other fields based on user input.

This example will show or hide the `regex_pattern` field when the user selects a different operator.

### **Additional Dynamic Field Controls**[**​**](https://docs.langflow.org/components-custom-components#additional-dynamic-field-controls)

You can also modify other properties within `update_build_config`, such as:

* `required`: Set `build_config["some_field"]["required"] = True/False`
* `advanced`: Set `build_config["some_field"]["advanced"] = True`
* `options`: Modify dynamic dropdown options.

### **Tips for Managing Dynamic Fields**[**​**](https://docs.langflow.org/components-custom-components#tips-for-managing-dynamic-fields)

When working with dynamic fields, consider the following best practices to ensure a smooth user experience:

* **Minimize field changes**: Hide only fields that are truly irrelevant to avoid confusing users.
* **Test behavior**: Ensure that adding or removing fields doesn't accidentally erase user input.
* **Preserve data**: Use `build_config["some_field"]["show"] = False` to hide fields without losing their values.
* **Clarify logic**: Add `info` notes to explain why fields appear or disappear based on conditions.
* **Keep it manageable**: If the dynamic logic becomes too complex, consider breaking it into smaller components, unless it serves a clear purpose in a single node.

## **Error handling and logging**[**​**](https://docs.langflow.org/components-custom-components#error-handling-and-logging)

In LLM Controls, robust error handling ensures that your components behave predictably, even when unexpected situations occur, such as invalid inputs, external API failures, or internal logic errors.

### **Error handling techniques**[**​**](https://docs.langflow.org/components-custom-components#error-handling-techniques)

**Raise Exceptions**:

If a critical error occurs, you can raise standard Python exceptions , such as `ValueError`, or specialized exceptions like `ToolException`. LLM Controls will automatically catch these and display appropriate error messages in the UI, helping users quickly identify what went wrong.

**Return Structured Error Data**:

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.

### **Improve debugging and flow management**

Use `self.status`: Each component has a status field where you can store short messages about the execution result , such as success summaries, partial progress, or error notifications. These appear directly in the UI, making troubleshooting easier for users.

Stop specific outputs with `self.stop(...)`: You can halt individual output paths when certain conditions fail, without affecting the entire component. This is especially useful when working with components that have multiple output branches.

**Log events**:

You can log key execution details inside components. Logs are displayed in the "Logs" or "Events" section of the component's detail view and can be accessed later through the flow's debug panel or exported files, providing a clear trace of the component's behavior for easier debugging.

### **Tips for error handling and logging**[**​**](https://docs.langflow.org/components-custom-components#tips-for-error-handling-and-logging)

To build more reliable components, consider the following best practices:

* **Validate inputs early**: Catch missing or invalid inputs at the start to prevent broken logic.
* **Summarize** **with** `self.status`: Use short success or error summaries to help users understand results quickly.
* **Keep logs concise**: Focus on meaningful messages to avoid cluttering the UI.
* **Return structured errors**: When appropriate, return `Data(data={"error": ...})` instead of raising exceptions to allow downstream handling.
* **Stop outputs selectively**: Only halt specific outputs with `self.stop(...)` if necessary, to preserve correct flow behavior elsewhere..
