Architecture Boundaries¶
Langflow is a runnable base application, a curated distribution, an executor SDK, standalone extensions, and one frontend. Dependencies point in one direction. Most "off-narrative" code is a boundary violation. Read this before adding a new file.
Package dependency graph (one-way only)¶
frontend (TS) ──HTTP──▶ langflow-base (UI/API, services, graph, db, alembic)
│
▼ may import
lfx (executor, primitives, built-in components)
│
▼ may import
langchain-core, pydantic
langflow (curated distribution) ──depends on──▶ langflow-base
│
└──depends on──▶ standalone lfx-* extensions ──depends on──▶ lfx
Dependency rules¶
lfxMUST NOT importlangflow.*. (langflow-baseinstalls thelangflowimport package.) If LFX code needs a service, define an interface insidelfxand inject the implementation from the application. Existing upward imports are known violations; do not add more.langflow-baseMAY importlfx. It MUST NOT import vendor-specific component modules fromlangflow.components.<vendor>.- Standalone
lfx-*extensions MAY import the public LFX bundle API. They MUST NOT import application services fromlangflow-base. langflowis dependency metadata, not another application layer. It adds the curated extension set to the samelangflow-baseexecutable and runtime.frontendtalks tolangflowonly via HTTP/WebSocket. No shared filesystem state.
"Where does this code go?" decision tree¶
Walk top-down. Stop at the first match.
- Is it framework-agnostic flow execution, base component classes, or
Componentprimitives? →src/lfx/src/lfx/(base/for shared primitives,components/for built-ins shipped with lfx). - Is it a FastAPI route, auth, db model, alembic migration, or a lifecycle-managed singleton?
→
src/backend/base/langflow/(api/,services/X/,alembic/versions/). - Is it a vendor integration (OpenAI, Pinecone, Notion, …) — a
Componentsubclass that wraps a third-party SDK? → a standalone package undersrc/bundles/<provider>/, with anextension.jsonmanifest. Add it to the curatedlangflowdependencies only when it belongs in the default distribution. Never rename the component class. - Is it UI, state, or icons?
→
src/frontend/src/. If it consumes a new API field, also updatesrc/frontend/src/types/. - Is it CLI behavior for
lfx run/lfx serve? →src/lfx/src/lfx/cli/. - Is it a SQLAlchemy/SQLModel model change?
→
services/database/models/ANDmake alembic-revision message="..."AND apply withmake alembic-upgrade. - Is it a flow JSON schema change? → STOP. Existing saved flows must keep loading. Add a version mapping; do not mutate the existing shape. See CONTRACTS.md.
- Is it shared by both
lfxandlangflow-base? →src/lfx/src/lfx/base/, neverlangflow/base/.
Dependency direction — bad/good examples¶
- Bad:
from langflow.services.deps import session_scopeinsidesrc/lfx/.... Good: Definelfx.interfaces.SessionProvider, accept it as a constructor arg;langflowwires the concretesession_scopeat startup. - Bad:
from langflow.components.openai import ...insidelangflow-basecore (api/,services/,graph/). Good: Components are loaded dynamically via the component registry; core code referencesComponentonly. - Bad: A new
MyHelperServicethat's just functions. Good: Utility functions go inlangflow/helpers/orlfx/utils/. A service inherits fromservices/base.Serviceand is registered viaservices/factory.py.
API change protocol¶
api/v1/is the live, stable surface (~25 routers). Existing v1 endpoints MUST stay backwards-compatible: only additive fields, never rename or remove.api/v2/is the active redesign surface (files,mcp,registration,workflow) — both are mounted at runtime inapi/router.py. v2 is not "future"; the old cursor rule was wrong.- Add a new endpoint to v2 only if it (a) replaces a v1 endpoint with a breaking shape change, or (b) belongs to one of the four v2 domains. Otherwise extend v1 additively.
- A breaking change to a v1 endpoint is forbidden. Add a v2 sibling and leave v1 in place.
Cross-cutting change protocol¶
A change that touches a request/response shape MUST update three places in the same PR:
- The pydantic model in
langflow/api/v{1,2}/schemas.py(or the route's local schema). - The TypeScript type in
src/frontend/src/types/consumed by the affected page/store. There is no OpenAPI generator — the types are hand-maintained, so the frontend silently breaks at runtime if you skip this. - If the field is persisted: a new alembic revision (
make alembic-revision message=...) AND a flow-JSON version mapping if the shape lives inside saved flows.
If you cannot do all three in one PR, do not start.
Service vs utility vs component¶
- Service (
services/<name>/): lifecycle-managed singleton, inheritsservices.base.Service, registered throughservices/factory.py, accessed viaservices/deps.py. Use when the thing has state, startup/shutdown, or shared connections (db, cache, queue). - Utility (
helpers/,utils/, orlfx/utils/): pure or near-pure functions. Use when there is no shared state and no lifecycle. - Component (
src/lfx/src/lfx/components/<category>/): user-visible node in the graph, subclass ofComponent, withdisplay_name,inputs,outputs. Use only when the user must wire it on the canvas. Do not add a Component to expose internal plumbing.
lfx/base/ vs langflow/base/¶
Both exist. Both have agents/, data/, models/, prompts/. New shared primitives go in src/lfx/src/lfx/base/. The langflow/base/ tree is legacy; do not add to it.