mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-24 05:16:40 +08:00
* docs: add directory depth limitation warning to custom components Add a warning admonition to the custom components documentation explaining the MAX_DEPTH=2 limitation for component discovery. The warning clarifies: - Components must be at most 2 levels deep (category/component.py) - Each first-level directory becomes an independent category in the UI - How to modify MAX_DEPTH for advanced use cases This addresses confusion around subdirectory support mentioned in issue #13102. * docs: simplify directory depth warning per review feedback Co-Authored-By: Antonio <antonio@oriontech.me> --------- Co-authored-by: Antonio <antonio@oriontech.me>
599 lines
23 KiB
Plaintext
599 lines
23 KiB
Plaintext
---
|
|
title: Create custom Python components
|
|
slug: /components-custom-components
|
|
---
|
|
|
|
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](#inputs-and-outputs) that determine data flow.
|
|
* Methods that define the component's behavior and logic.
|
|
* Internal variables for [Error handling and logging](#error-handling-and-logging)
|
|
|
|
Use the [Custom component quickstart](#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](/environment-variables), 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
|
|
```
|
|
|
|
:::warning Directory Depth Limitation
|
|
|
|
Components can be at most 2 levels deep.
|
|
|
|
For example, `data/custom_component.py` is discoverable, but `data/tools/custom_component.py` is not discoverable.
|
|
|
|
To increase the depth limit, modify `MAX_DEPTH` in `src/lfx/src/lfx/custom/directory_reader/directory_reader.py`. Subdirectories will still appear as separate categories, not nested hierarchies.
|
|
|
|
:::
|
|
|
|
### 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](https://github.com/langflow-ai/langflow/blob/main/src/lfx/src/lfx/components/FAISS/__init__.py).
|
|
|
|
</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](#custom-component-path).
|
|
|
|
```
|
|
/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](/concepts-components#component-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](https://github.com/langflow-ai/langflow/blob/main/src/lfx/src/lfx/inputs/input_mixin.py) in the Langflow codebase.
|
|
|
|
For a list of all available input types, see the [input type definitions](https://github.com/langflow-ai/langflow/blob/main/src/lfx/src/lfx/inputs/inputs.py) 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 `Table`:
|
|
|
|
```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 `Table`. 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](https://github.com/langflow-ai/langflow/blob/main/src/lfx/src/lfx/template/field/base.py) in the Langflow codebase. Common parameters include:
|
|
|
|
**Additional return types:**
|
|
* **`Message`**: Structured chat messages
|
|
* **`JSON`**: Flexible object with `.data` and optional `.text`
|
|
* **`Table`**: 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`, `JSON`, or `Table`, 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 `JSON` 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 `JSON`, `Message`, or `Table` 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 `JSON`).
|
|
|
|
```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 `Table`).
|
|
|
|
```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 `JSON` 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 `JSON` 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](https://www.structlog.org) for application-level logging that outputs to `langflow.log` files. For more information, see [Logs](/logging).
|
|
|
|
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](/contributing-components). |