docs/agents/COMPONENTS.md
Components are the unit of work in Langflow. They render as nodes on the canvas, persist into flow JSON, and become tools for Agents. Get the contract right and the rest of the system follows; get it wrong and you orphan every saved flow that referenced your component.
Components live in src/lfx/src/lfx/components/<category>/. The mirror tree under src/backend/base/langflow/components/ is legacy stubs — do not add files there.
Stop and check, in order. If any answer is "yes," do not create a new component.
rg -l 'display_name = ".*<keyword>.*"' src/lfx/src/lfx/components/
tools/calculator.py legacy = True, replacement = ["helpers.CalculatorComponent"]) is what happens when this step is skipped.tool_mode=True? If yes, add tool_mode=True to the relevant input on the existing component instead of building a new one.openai/, anthropic/, datastax/) rather than in generic tools/ or helpers/? Vendor logic goes in vendor folders so bundles can ship/version independently.langflow.services.* from a component.LCModelComponent (see openai/openai_chat_model.py)LCToolComponent (see tools/calculator.py)LCVectorStoreComponent + @check_cached_vector_store (see vectorstores/local_db.py)ChatComponent (see input_output/chat.py)advanced=True. If you need a mode switch (Ingest/Retrieve etc.), use TabInput + update_build_config to hide irrelevant fields — see vectorstores/local_db.py.Output(method=...) is the default. Add more only if downstream nodes genuinely need different shapes (Message vs DataFrame vs Data). Don't add an output you don't use.tool_mode=True (e.g., local_db.py) — this is how a component becomes an agent tool.Saved flows reference components by string identifiers. Once a component is merged, the following are frozen — change them and existing user flows break silently:
name = "..." class attribute. This is the flow-JSON identifier, not the class name. See openai_chat_model.py, chat.py.name=.... Renaming input_value → prompt orphans every edge pointing at it.name=... and its declared return type. Downstream nodes match on both.To remove or rework a component, do not edit it in place. Add the replacement under the right category, then on the old one set:
legacy = True
replacement = ["<category>.<NewClassName>"]
(see tools/calculator.py, flow_controls/sub_flow.py). The UI shows "Updates Available" and migrates users; the old class stays importable.
See CONTRACTS.md for the full user-facing surface this protects.
These appear all over the codebase but rarely in old docs.
name (class attr): flow-JSON identifier. Always set it explicitly; do not rely on __class__.__name__. Example: chat.py name = "ChatInput".legacy = True + replacement = [...]: deprecation pair. Always together. Example: tools/calculator.py.tool_mode=True on an input: exposes the component as an Agent tool with that input as the tool argument. Example: vectorstores/local_db.py.real_time_refresh=True + update_build_config(self, build_config, value, name): dynamic form. Use for mode switches and dependent-field hide/show. Examples: openai/openai_chat_model.py, vectorstores/local_db.py.metadata = {"keywords": [...]}: extra search terms for the component picker. Example: data_source/sql_executor.py.minimized = True: render collapsed by default. Example: input_output/chat.py.documentation = "https://docs.langflow.org/...": deep-link from the node UI. Example: input_output/chat.py.openai/, anthropic/, datastax/, cohere/, …). Not tools/, not models/.helpers/, processing/, logic/, flow_controls/.input_output/.__init__.py in alphabetical order.Every component needs an icon. Use a Lucide icon when one fits; create a custom SVG only for vendor logos.
icon = "calculator" # any Lucide icon name, lowercase
See https://lucide.dev/icons for the catalog.
For brand logos you need a frontend SVG component. The Python icon string and the frontend mapping key must match exactly (case-sensitive).
icon = "AstraDB" on the component.src/frontend/src/icons/AstraDB/AstraDB.jsx:
const AstraSVG = (props) => (
<svg {...props}>
<path fill={props.isDark ? "#ffffff" : "#0A0A0A"} d="..." />
</svg>
);
src/frontend/src/icons/AstraDB/index.tsx:
import React, { forwardRef } from "react";
import AstraSVG from "./AstraDB";
export const AstraDBIcon = forwardRef<SVGSVGElement, React.PropsWithChildren<{}>>(
(props, ref) => <AstraSVG ref={ref} isDark={isDark} {...props} />
);
src/frontend/src/icons/lazyIconImports.ts:
AstraDB: () =>
import("@/icons/AstraDB").then((mod) => ({ default: mod.AstraDBIcon })),
icon = "..." set.isDark prop, wrapper with forwardRef, entry in lazyIconImports.ts.See TESTING.md for the full testing contract. Quick reference:
ComponentTestBaseWithClient (needs API) or ComponentTestBaseWithoutClient (pure logic).component_class, default_kwargs, file_names_mapping.MockLanguageModel for pure-logic LLM paths; use @pytest.mark.api_key_required for real-API tests..set(), async_start, validate. Do not poke graph internals.When in doubt, read these files before starting:
src/lfx/src/lfx/components/openai/openai_chat_model.pytool_mode and legacy/replacement: src/lfx/src/lfx/components/tools/calculator.pysrc/lfx/src/lfx/components/vectorstores/local_db.pyminimized: src/lfx/src/lfx/components/input_output/chat.pysrc/lfx/src/lfx/components/data_source/sql_executor.pysrc/lfx/src/lfx/components/flow_controls/sub_flow.py