langflow-ai/langflow
github.com/langflow-ai/langflow ↗ · 3997 files · 32.9 MB · scan capped
Langflow is a powerful platform for building and deploying AI-powered agents and workflows.
Overview
Langflow is a visual builder and runtime for AI/LLM workflows (agents, RAG pipelines, tool chains): users compose 'components' into a flow graph on a React canvas, persist the graph as JSON in a database, and execute it through a FastAPI backend that compiles the payload into a vertex graph and runs it against a pluggable component catalog. The repo is a uv monorepo split into lfx (headless executor, shared primitives, built-in components, its own `lfx`/`lfx-mcp` CLIs), langflow-base (the FastAPI app, service layer, auth, SQLModel/Alembic persistence), the curated `langflow` distribution that adds lfx-* provider bundles, a React 19 + Vite frontend, and a `langflow-sdk` Python client. Its distinctive shape is a heavily-pluginized service layer: auth, authorization, catalog/model-provider policy and even routes are replaceable through entry points and lfx.toml config.
System map
Langflow ships as a uv workspace whose public dependency direction is langflow -> langflow-base -> lfx, plus ~24 lfx-* provider bundle packages and a React/Vite frontend. The primary entrypoint is the `langflow` console script (langflow_launcher -> Typer app in __main__.py -> `langflow run`), which boots a FastAPI app created by create_app() in main.py and served by gunicorn on Linux or direct uvicorn on Windows/macOS. All cross-cutting capabilities (auth, DB, cache, storage, telemetry, tracing, job queue, ...) live behind a singleton ServiceManager in lfx with factory registration plus entry-point/config-file plugin discovery. Persistence is SQLAlchemy 2 async + SQLModel (aiosqlite default, Postgres supported) migrated by Alembic. The graph execution engine (Graph, Vertex, Edge) lives in lfx and is shared by the server and the standalone `lfx run`/`lfx serve` CLIs.
- The `langflow` console script resolves to langflow.langflow_launcher:main; on Darwin it re-execs `python -m langflow.__main__` after setting OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES because Objective-C fork-safety would otherwise SIGSEGV gunicorn workers.
- `langflow.__main__` is a Typer app exposing commands run, superuser, api_key, copy_db, migration, migrate-mcp, reconcile-kb-from-disk; it mounts an `lfx` sub-app (serve/run) and an `observability` group from lfx.cli.
- create_app() in main.py builds the FastAPI app: ContentSizeLimit + Sentry + CORS middleware, JavaScriptMIMEType fix, an execution-client header binder, a multipart-boundary validator for /api/v1/files/upload, X-Forwarded-Prefix handling, comma-flattened query strings, and Accept-Language locale resolution; it mounts router (/api/v1, /api/v2), health_check_router, log_router, then plugin routes.
- The uv workspace members are src/backend/base (langflow-base), the root (langflow), src/lfx, src/langflow-stepflow, src/sdk, and ~24 src/bundles/* packages; the curated distribution declares bounded ranges for lfx-* bundles (e.g. lfx-openai>=0.1.4,<1.0.0) while lfx-bundles stays opt-in.
- The documented package layering is frontend ->HTTP-> langflow-base -> lfx -> langchain-core/pydantic, with lfx forbidden from importing langflow.* and vendor components living only in src/bundles/<provider> packages.
- All services hang off a singleton ServiceManager (lfx/services/manager.py) created lazily via get_service_manager(); register_all_service_factories() in langflow/services/utils.py registers ~25 factories (settings, cache, chat, database, session, storage, variable, telemetry, tracing, transaction, state, checkpoint, job_queue, task, store, shared_component_cache, auth, authorization, policy_bundle, catalog_policy, model_provider_policy, mcp_composer, executor).
- langflow/services/deps.py exposes typed getters (get_db_service, get_auth_service, get_queue_service, ...) and the canonical async session_scope() context manager; get_session is deprecated and raises NotImplementedError. get_auth_service() defensively swaps out lfx's no-op AuthService stub for Langflow's JWT implementation.
- Persistence is SQLModel/SQLAlchemy async (aiosqlite default via LANGFLOW_DATABASE_URL=sqlite:///./langflow.db, Postgres via postgresql://) with Alembic migrations in langflow/alembic; a Postgres advisory lock (0x4C616E67666C6F77) serializes concurrent `alembic upgrade` across workers.
- The execution engine is lfx.graph.graph.base.Graph (~3.2k lines) with from_payload(), arun(), async_start(), build_vertex(), prepare(), RunnableVerticesManager, checkpoint support (GraphCheckpoint), OTel `flow.execute` spans, and per-run state (run_id, session_id, end_user_id, persist_messages).
- The frontend is React 19 + TypeScript + Vite + Zustand stores (flowStore, authStore, ...) + @xyflow/react canvas, packaged as `langflow` v1.12.2 in src/frontend/package.json; the built SPA is served by the backend via setup_static_files() which reserves /api GET/HEAD to return JSON 404s instead of the SPA shell.
- Deployment shape: `langflow run` uses gunicorn multi-worker fork on Linux and direct uvicorn (clamped to 1 worker) on Windows/macOS; ensure_multi_worker_safe() refuses to boot with >1 worker on the default in-memory job queue unless LANGFLOW_JOB_QUEUE_TYPE=redis or LANGFLOW_DANGEROUSLY_ALLOW_MULTI_WORKER_WITHOUT_SHARED_QUEUE=true.
- Observability is wired through lfx.observability: instrument_fastapi_app(app), execution_client/execution_protocol contextvars, an event-loop lag monitor, DB pool metrics, a Prometheus HTTP server gated by LANGFLOW_PROMETHEUS_PORT, and optional sentry_sdk init from LANGFLOW_SENTRY_DSN inside the lifespan.
Feature catalog
The v1 API (~35 routers under /api/v1) covers flow CRUD and sharing, graph build/run (job-queue + SSE), webhooks, files, variables, users/API keys, folders/projects, knowledge bases, memories, monitor/traces, a Langflow Store client, voice mode, A2A, an OpenAI-compatible responses route, MCP servers (global and per-project), and a full authz administration surface (shares, roles, assignments, teams, audit, me). v2 under /api/v2 hosts the workflow execution contract (sync/stream/background runs via a shared lfx router bound to a Langflow host), v2 files/MCP, and registration. Beyond HTTP there is the `lfx` executor CLI (run/serve a flow without the DB), `lfx-mcp`, a Python SDK, and a feature-gated 'agentic experience' with its own routers.
- Flow management: api/v1/flows.py implements POST/GET/PATCH/PUT upsert/DELETE plus batch create, upload/download of flow JSON, basic_examples, and a note_translations + expand_compact_flow endpoint; flow rows carry name, data JSON, endpoint_name, webhook/mcp_enabled flags, access_type PRIVATE|PUBLIC, folder/workspace scoping.
- Synchronous and streaming flow execution: POST /api/v1/run/{flow_id_or_name} (and a session-scoped variant) plus POST /api/v1/webhook/{flow_id_or_name} with an SSE webhook-events stream; all funnel through _run_flow_internal -> simple_run_flow.
- Editor build pipeline: POST /api/v1/build/{flow_id}/flow returns a job_id; GET /api/v1/build/{job_id}/events polls SSE (or event_delivery=direct streams inline); plus cancel_build, deprecated vertex-order/vertex-build endpoints, and public unauthenticated variants build_public_tmp.
- v2 workflow API: POST /api/v2/workflows is owned by the shared lfx.workflow.router.create_workflow_router bound to LangflowWorkflowHost, which supplies resolve_caller (session cookie/API key), get_flow (share-aware fetch), authorize (WorkflowAction->FlowAction), run_sync, stream_response, and submit_background; langflow keeps its own GET status/POST stop/GET events durable routes.
- MCP integration is bidirectional: api/v1/mcp.py runs an MCP server exposing flows as tools over SSE (SseServerTransport) and Streamable HTTP (StreamableHTTPSessionManager, started in the lifespan), api/v1/mcp_projects.py runs per-project MCP servers, and an MCPComposerService plus an agentic MCP router exist; client-side MCP sessions are pooled by MCPSessionManager tunables.
- Authentication surface: POST /api/v1/login (OAuth2 password form -> JWT access+refresh), GET /auto_login, POST /refresh, /logout, users CRUD, api_key CRUD; API-key auth is via x-api-key header/query validated against DB keys or a single LANGFLOW_API_KEY when LANGFLOW_API_KEY_SOURCE=env.
- RBAC/authorization administration: /api/v1/authz/* routers (shares CRUD with an OSS owner floor, roles, role assignments, teams, me, and a superuser-only audit query capped at 200/page) sit behind guards in services/authorization; the feature is off by default (LANGFLOW_AUTHZ_ENABLED=false) with an OSS pass-through service.
- File handling: /api/v1/files upload/download (multipart boundary validated by middleware) and v2 files; the StorageService abstracts local disk vs S3 (services/storage/local.py, s3.py).
- Governance/policy surfaces: model_provider_policy, catalog_policy, policy_bundle routers administer which providers/components a project may use; a model_provider_policy_refresh_worker runs per worker at startup.
- Interoperability endpoints: voice_mode router (voice), a2a.py (agent-to-agent protocol, gated per-request by LANGFLOW_A2A_ENABLED), openai_responses.py (OpenAI-compatible /responses), and a store.py client to api.langflow.store for sharing components.
- Feature-gated 'agentic experience': _include_agentic_router() mounts agentic_router plus files/sessions routers behind require_agentic_experience and an agentic MCP router; sandbox-management endpoints share the gate while /agentic/check-config stays readable.
- lfx CLI is a standalone Typer app (`lfx`, plus `lfx-mcp`) with registered command groups: setup, observability, prewarm, authoring, upgrade, extension, running (`lfx run`, `lfx serve` which serves flow JSON/Python scripts as an HTTP API via a FlowRegistry), and remote.
Data flow
The core data path is: flow JSON (nodes+edges) persisted in the `flow` SQLModel table -> loaded owner-scoped or share-aware -> optionally sanitized by caller-aware component policy -> tweaks applied -> Graph.from_payload compiles vertices/edges -> graph.arun executes vertices and emits events -> RunResponse/SSE to the caller while messages, transactions, and vertex_builds rows persist execution history. Secrets (global variables, API keys) are Fernet-encrypted at rest using LANGFLOW_SECRET_KEY. Files go through a local/S3 StorageService namespaced per flow. Components themselves are discovered at boot from built-ins, components_path dirs, extension bundles, and remote bundle URLs, and cached in component_cache.
- Flow persistence: Flow rows store the graph as `data` JSON (nodes/edges/viewport) with name/endpoint_name/access_type/webhook/mcp_enabled metadata; cascade_delete_flow manually deletes MessageTable, TransactionTable, VertexBuildTable, FlowVersion, SpanTable/TraceTable, AuthzShare and the Flow row because SQLite FK cascades are not guaranteed.
- Graph materialization: simple_run_flow loads flow.data, applies caller-aware component policy (prepare_flow_build_for_user), then either serves a warm-registry template (try_warm_run_graph) or runs process_tweaks + apply_global_variable_defaults + Graph.from_payload(payload, flow_id, user_id, context).
- Tweaks and targeted inputs mutate the graph payload pre-instantiation via lfx.processing.process.process_tweaks/validate_targeted_inputs; refused tweaks raise TweakRefusedError mapped to a structured 422 (code TWEAKS_REFUSED) by an app-level handler, and non-owners are blocked from tweaking stored flows by _enforce_owner_only_tweaks.
- Execution: run_graph_internal sets graph.session_id then awaits graph.arun(inputs..., outputs, stream, event_manager); results return as list[RunOutputs] -> RunResponse; v1 responses and SSE frames re-project content_blocks via project_payload_to_v1 to keep the 1.11 wire shape.
- Streaming events: build/run events are produced into an asyncio.Queue owned by JobQueueService.create_queue(job_id) and consumed by get_build_events/consume_and_yield; a RedisJobQueueService backend (LANGFLOW_JOB_QUEUE_TYPE=redis) shares queues across gunicorn workers and is connectivity-probed at startup.
- Credential storage: DatabaseVariableService stores global variables; CREDENTIAL_TYPE values are Fernet-encrypted/decrypted via auth service helpers keyed on LANGFLOW_SECRET_KEY — short secrets derive the key with SHA-256, and a MultiFernet retains a legacy PRNG-derived key for decrypting pre-1.10.1 ciphertext.
- File inputs for public flows are restricted to the `{source_flow_id}/{basename}` namespace by validate_public_files (mitigation for GHSA-rcjh-r59h-gq37), and session IDs for anonymous public runs are namespaced per (client_id, flow_id) by scope_session_to_namespace / compute_virtual_flow_id (CVE-2026-33017).
- Component discovery at boot: lifespan calls get_and_cache_all_types_dict (lfx.interface.components) which merges built-in lfx components, settings.components_path directories, extension components (import_extension_components over DiscoveredExtension records), and bundles downloaded from settings.bundle_urls into temp dirs by load_bundles_from_urls.
- Execution history is persisted per run: JobService.create_job writes a WORKFLOW job row, vertex builds and transactions land in VertexBuildTable/TransactionTable, and startup cleanup prunes both tables plus authz_audit_log to configured retention (max_transactions_to_keep, max_vertex_builds_to_keep, AUTHZ_AUDIT_RETENTION_DAYS).
- Per-request identity context: session cookie/API-key auth resolves through get_current_user_for_workflow/api_key_security; a serving-plane end-user header is merged into the effective session_id by resolve_serving_scope and stamped on graph.end_user_id while execution still runs as flow.user_id; execution_client/execution_protocol contextvars label the surface for tracing.
- Durable/background runs: POST /api/v2/workflows mode=background dispatches to LangflowWorkflowHost.submit_background -> submit_background_with_mapping, with HITL pause/resume supported by a CheckpointService (lfx.graph.checkpoint CheckpointStore) and orphan sweeping on startup via background_execution service.
- Outbound calls a boot makes: models.dev catalog refresh (daily task, disabled by LANGFLOW_MODELS_DEV_REFRESH=false), bundle URL fetches, store webhooks (LANGFLOW_DOWNLOAD_WEBHOOK_URL/LIKE_WEBHOOK_URL), GitHub archive downloads via detect_github_url, and version checks via fetch_latest_version.
Behavior traces
Traced end-to-end: (1) `langflow run` startup from console script through lifespan service init; (2) a synchronous POST /api/v1/run/{flow} execution; (3) the editor's build+events flow; (4) flow deletion cascade; (5) v2 workflow dispatch; (6) shutdown ordering. Each hop below names the actual functions in call order.
- Startup trace: `langflow` script -> langflow_launcher.main (Darwin re-exec for OBJC fork safety) -> typer `run` command -> env-file load -> production preflight + Postgres version check -> ProcessManager spawns gunicorn (Linux) or uvicorn (win/mac, workers clamped to 1) -> create_app() -> get_lifespan().
- Lifespan init order: configure() logging -> optional sentry_sdk.init -> ensure_production_preflight -> initialize_services (register_all_service_factories -> initialize_database + alembic -> hydrate_policy_bundle -> hydrate_catalog_policy -> setup_superuser -> clean_transactions/vertex_builds/authz_audit_log) -> initialize_environment_variables -> telemetry writer + audit_cleanup + model_provider_policy_refresh workers -> setup_llm_caching -> profile pictures -> KB/memory-base backfills -> Prometheus -> load_bundles_from_urls -> get_and_cache_all_types_dict -> create_or_update_starter_projects (FileLock) -> agentic globals -> telemetry_service.start -> MCPComposerService.start -> MCP server backfill -> load_flows_from_directory -> warm registry task -> sync_flows_from_fs + queue_service.start -> orphan sweep -> delayed init_mcp_servers -> streamable-HTTP managers -> lag monitor + DB pool instrumentation -> enterprise hooks.
- Superuser bootstrap: setup_superuser branches on AUTO_LOGIN — when on, it ignores a legacy default password, generates token_urlsafe(32), takes a FileLock (langflow_auto_login_superuser.lock), creates the user via auth.create_super_user, initializes variables and the default folder; when off, it requires configured credentials and deletes an unused default superuser on teardown.
- Sync run trace: POST /api/v1/run/{flow_id_or_name} -> api_key_security / get_current_user -> get_flow_by_id_or_endpoint_name (widen_for_shares) -> ensure_flow_permission(EXECUTE) -> _run_flow_internal -> _enforce_owner_only_tweaks -> resolve_serving_scope -> simple_run_flow -> set_current_model_provider_policy_context -> prepare_flow_build_for_user -> try_warm_run_graph or (process_tweaks -> raise_if_hitl_unsupported -> apply_global_variable_defaults -> Graph.from_payload) -> graph.set_run_id -> JobService.create_job + execute_with_status -> run_graph_internal -> graph.arun -> RunResponse -> _v1_run_response (content_blocks projection).
- Streaming run trace: _run_flow_internal(stream=True) creates an asyncio.Queue + client_consumed_queue, wraps them in a tokens EventManager, spawns run_flow_generator as a task (simple_run_flow(stream=True) -> event_manager.on_end/on_error -> sentinel None), and returns StreamingResponse(consume_and_yield(...)) with an on_disconnect cancel.
- Editor build trace: POST /api/v1/build/{flow_id}/flow -> session_scope _read_flow (owner-scoped, PUBLIC fallback) -> ensure_flow_permission(EXECUTE) with deny->404 reframe unless READ passes -> resolve_flow_data_override drops caller data unless flow:write -> prepare_flow_build_for_user sanitizes stored/inline data -> start_flow_build -> validate_targeted_inputs -> queue_service.create_queue(job_id) -> generate_flow_events task (build_graph_from_db/build_graph_from_data -> graph.prepare -> per-vertex build emits VertexBuildResponse events) -> returns {job_id} -> client polls GET /build/{job_id}/events -> _register_job_owner_or_cancel records ownership for cancel.
- Flow deletion trace: DELETE /api/v1/flows/{flow_id} -> owner/authz check -> cascade_delete_flow -> check_flow_has_deployed_versions guard -> delete messages -> transactions -> vertex_builds -> flow_versions -> spans then traces (FK order) -> authz_share rows -> flow row; rowcount==1 verifies removal.
- v2 workflow trace: POST /api/v2/workflows -> lfx create_workflow_router -> LangflowWorkflowHost.resolve_caller (oauth2_login cookie -> api_key query -> header -> get_current_user_for_workflow, in its own session_scope to avoid SQLite contention) -> get_flow (resolve_flow_for_execution) -> authorize (authorize_flow_action, deny->404) -> branch: run_sync_with_mapping (job-tracked), build_stream_response (v1 build-vertex loop + AG-UI), or submit_background_with_mapping (durable).
- Service resolution trace: get_service(ServiceType.X) -> ServiceManager.get (keyed lock) -> first-call discover_plugins (entry points `lfx.services` override=False, then lfx.toml/pyproject `[tool.lfx.services]` override=True) -> service_classes direct registration wins -> _create_service_from_class inspects __init__ params and recursively resolves ServiceType dependencies -> otherwise _create_service_from_factory resolves factory.dependencies then factory.create() + set_ready().
- Shutdown trace: lifespan finally -> cleanup_mcp_sessions FIRST (kill MCP subprocesses) -> enterprise shutdown hooks -> stop_event_loop_lag_monitor -> staged progress (stop server -> cancel sync_flows_from_fs/mcp_init/models_dev/warm_registry tasks + stop streamable-HTTP/project/agentic MCP managers + A2A push client + audit/policy workers) -> drain_pending_audit_writes -> teardown_services (30s cap; teardown_superuser deletes unused default superuser when AUTO_LOGIN off) -> shutdown_sandbox (QEMU guests) -> temp_dir cleanup -> summary.
Testing signals
Backend tests are pytest with asyncio_mode=auto across three roots (src/backend/tests ~778 files, src/lfx/tests ~425, src/bundles/*/tests ~76), with a rich marker taxonomy and heavyweight plugins (blockbuster for blocking-call detection, pytest-timeout in thread/faulthandler mode, xdist, rerunfailures, flakefinder, hypothesis, pyleak). The conftest boots the real app via create_app/LifespanManager over ASGITransport and StaticPool SQLite. Frontend uses Jest unit tests plus Playwright e2e (a11y/core/extended/live suites) with merged nyc coverage. CI runs a Python matrix (3.10/3.14) with split groups, bundle-guarded jobs, template tests, docker tests, CodeQL, and DB-migration validation.
- pytest is configured at the repo root: testpaths = src/backend/tests, src/lfx/tests, src/bundles/*/tests; asyncio_mode=auto; per-test timeout=90s with timeout_method=thread (temporary diagnostic for a Group-3 hang that dumps all thread stacks); addopts disables the benchmark plugin.
- Markers declared: async_test, api_key_required (deselected in CI via `-m "not api_key_required"` — those tests hit live provider APIs), no_blockbuster, benchmark, unit, integration, slow, security (IDOR/auth/access-control regression), real_services (needs real SQLite+Postgres+Redis).
- Dev-group test tooling is extensive: blockbuster (blocking-call detection in async), pytest-xdist/-sugar/-instafail/-cov/-split/-flakefinder/-rerunfailures/-timeout/-profiling, respx (httpx mocks), hypothesis, pyleak (leak detection), locust (load), fakeredis, codeflash, and mcp-server-fetch.
- The backend conftest builds a real app: TestClient/AsyncClient over ASGITransport + asgi-lifespan LifespanManager around create_app(), a StaticPool async engine for SQLite, blockbuster_ctx, and api-key fixtures; tests are instructed to prefer real integrations over mocks.
- Component tests have dedicated base classes (ComponentTestBaseWithClient / ComponentTestBaseWithoutClient) requiring component_class, default_kwargs, file_names_mapping fixtures, living under src/backend/tests/unit/components.
- Frontend testing: Jest (`npm test`) plus Playwright e2e under src/frontend/tests (a11y, core, extended, live, assets, fixtures), jest coverage and playwright-coverage merged via nyc and uploaded to Codecov; a11y-scan and a11y-unit-tests workflows exist.
- CI Python job matrixes Python 3.10 and 3.14, splits unit tests into groups via `make unit_tests args="--splits N --group G --reruns 5 --cov"`, and a separate job installs the bundles extra and runs only tests guarded by pytest.importorskip('lfx_bundles').
- Specialized CI: cross-platform-test, cross-bundle-test (bundle dependency range resolution), db-migration-validation, template-tests (runs test_starter_projects.py against all starter templates), docker_test/docker builds, CodeQL, smoke-tests, and stress-tests workflows.
- Integration tests are segregated under src/backend/tests/integration (coverage config ignores them: `--ignore=tests/integration`); there are also locust load tests (src/backend/tests/locust), a stress dir exempted from normal lint rules, and a `regressions/` folder of per-version YAML specs (1.9.x-1.12.x).
- Known test fragility is documented: DB tests 'may fail in batch but pass individually'; tests inside sub-packages need `uv sync --group dev --package <pkg>` first or dev deps like fakeredis are missing; tests set LANGFLOW_MODELS_DEV_REFRESH=false because the background fetch hits the network and trips pyleak.
Risk map
The dominant risk theme is executing caller-influenced code: custom components are arbitrary Python exec'd at build/validation time, stored flow JSON embeds component source, and multiple policy layers (custom_component_admin_only, tweak policy, owner-only tweaks, catalog/model-provider policy) exist to contain that. Auth surface risk is managed but default-lenient (AUTO_LOGIN, permissive CORS with warnings, SSRF protection off by default, short SECRET_KEY tolerated with legacy decryption). Supply-chain and infra footguns include remote bundle zips at startup, entry-point service replacement, per-worker in-memory job queues, and pickle in the cache service.
- Custom components execute arbitrary Python: lfx/custom/validate.py calls exec() on component code in several places (function extraction, class validation), explicitly noqa'd S102; code execution tools (python_repl) run user code gated by ensure_code_execution_enabled() + LANGFLOW_ALLOW_CUSTOM_COMPONENTS / LANGFLOW_BLOCK_CODE_INTERPRETER_COMPONENTS.
- Stored flows carry component source: a regular user can persist component code via the flow-write API then execute it — mitigated by prepare_flow_build_for_user applying custom_component_admin_only policy to stored bytes on a detached copy before Graph.from_payload in both /run and /build paths.
- An opt-in QEMU microVM sandbox backend (LANGFLOW_SANDBOX_BACKEND=exec-sandbox, extra `langflow-base[sandbox]`) exists for code-execution components; plugin backends are gated by LANGFLOW_SANDBOX_BACKEND_PLUGINS; shutdown_sandbox is called in lifespan cleanup so recycled workers don't leak VMs.
- SSRF protection is OFF by default (LANGFLOW_SSRF_PROTECTION_ENABLED unset/false 'for backward compatibility'); when enabled it blocks private IP/localhost/metadata endpoints with an allowlist LANGFLOW_SSRF_ALLOWED_HOSTS — a meaningful default-off posture for a tool that fetches URLs.
- CORS defaults are permissive: wildcard origin + credentials historically enabled credentialed cross-site requests; code now force-disables credentials when '*' is present and warns that stricter defaults arrive in v2.0 — operators on current versions still get all-origins by default.
- AUTO_LOGIN creates a superuser with a generated random password (legacy default password is ignored/rotated) guarded by a FileLock; teardown deletes the unused default superuser when AUTO_LOGIN is off — the warning text notes auto-login mode should not be exposed since access implies code execution.
- SECRET_KEY handling: short secrets derive the Fernet key via SHA-256 for new encryption, but a MultiFernet retains a deterministic random.Random(seed)-derived legacy key for decryption — deployments with weak SECRET_KEYs keep weak key derivation, only warned about (MINIMUM_SECRET_KEY_LENGTH=32 warning).
- Remote bundle URLs (settings.bundle_urls) download zip archives (incl. GitHub repos) at startup and extract component files into temp dirs that are added to components_path — remote code is fetched and executed; errors are logged but never block startup.
- Entry-point plugin loading replaces service classes: any installed distribution can register `lfx.services` entry points; subclass validation exists only for AUTHORIZATION/CATALOG_POLICY/MODEL_PROVIDER_POLICY/POLICY_BUNDLE services, and lfx.toml config paths get override=True — a malicious/misconfigured package can swap less-guarded services.
- Multi-worker state hazards are extensive: in-memory job queues break POLLING/STREAMING event delivery (~half of GET /build/<job_id>/events land on the wrong worker), MCP-over-SSE is broken, rate limits multiply per worker, orphan sweep is node-local — the guard refuses boot but the bypass env var exists.
- Pickle is used intentionally in the cache service (ruff S301 waived for services/cache/*) — safe for local memory/SQLite but a deserialization risk if pointed at an untrusted/shared backend.
- worker.py is vestigial Celery code: test_celery/build_vertex tasks exist but process_graph_cached_task raises NotImplementedError('This task is not implemented yet') — dead execution path kept in the tree.
Extension points
Langflow is built to be extended at five seams: (1) component/extension bundles via the `langflow.extensions` entry point + extension.json manifests + seed dirs + components_path; (2) service replacement via `lfx.services` entry points and lfx.toml/pyproject config (auth SSO, authorization/RBAC, catalog policy, model-provider policy, policy bundle); (3) HTTP route plugins via the `langflow.plugins` entry point with conflict-protected registration; (4) CLI plugins via register_cli_command_plugins; (5) enterprise hooks via the lifespan hook registry. Custom components subclass lfx Component and are discovered from directories.
- Route plugins: distributions registering a `langflow.plugins` entry point get a callable register(app) invoked with a _PluginAppWrapper that reserves every existing (path, method), refuses shadowing (RouteConflictError), and rolls back a half-registered plugin's routes on failure.
- Service plugins: the `lfx.services` entry-point group registers service classes (override=False) and `lfx.toml`/`pyproject.toml [tool.lfx.services]` entries register with override=True; precedence is config > decorator > entry point, SETTINGS_SERVICE can never be overridden, and misconfigured MODEL_PROVIDER_POLICY/POLICY_BUNDLE services refuse to boot rather than fall back.
- The OSS authorization layer is a designed plugin seam: LangflowAuthorizationService is a registered pass-through (every check allows) replaced by listing a real plugin class under `authorization_service` in lfx.toml — the same pattern documented for SSO via `auth_service`.
- Component/extension bundles: lfx-* packages ship an extension.json manifest inside the wheel and register a `langflow.extensions` entry point; discovery also scans LANGFLOW_SEED_DIR (default /opt/langflow/bundles) directories; inline bundles can come from components_path parents.
- Custom components: subclass lfx.custom.custom_component.component.Component (re-exported as langflow.custom.Component), declare display_name/inputs/outputs, drop the file under a components_path directory; LFX_DEV=1 / LFX_DEV=<modules> enables dynamic hot loading; directory_reader scans the paths.
- Enterprise lifespan hooks: modules can append async callables to _enterprise_lifespan_hooks['startup'|'shutdown'] at app-construction time; startup hooks run after all services init, shutdown hooks run first in teardown; failures are logged and never block boot.
- CLI plugins: lfx.cli.command_plugins.register_cli_command_plugins is invoked by the langflow Typer app so installed packages can add CLI subcommands; lfx's own commands are modular register(app) functions per group.
- Deployment adapters/mappers: register_builtin_adapters + register_builtin_deployment_mappers import provider modules (e.g. watsonx_orchestrate) which register onto AdapterRegistry singletons — gated behind FEATURE_FLAGS.wxo_deployments.
- Sandbox backend plugins: LANGFLOW_SANDBOX_BACKEND_PLUGINS whitelists which entry-point plugins may load an executor backend for code-execution components; the default backend is 'none'.
- Frontend white-label seams: CustomRoutesStore/CustomAuthRoutesStore/CustomRoutesStorePages in routes.tsx let deployments override routes/pages, and icons register via lazyIconImports; the frontend only talks to the backend over HTTP/WS.
Contributor handoff
Onboarding essentials: this is a uv workspace — always `uv run` Python commands, sync sub-package dev groups before testing inside them, run `make format_backend` before committing (ruff select=ALL, line-length 120, big per-file-ignore table), and respect the one-way dependency direction (lfx never imports langflow.*). Component class names are stable identifiers — never rename. Flow JSON shape is a contract. DB model changes require alembic revisions. The service layer is registry-driven: add services via factories + ServiceType, not globals. Highest-risk edit zones: main.py lifespan ordering, endpoints.py/build.py/chat.py auth+policy ordering, and anything that executes stored component code.
- Read first: AGENTS.md (commands + component rules), docs/agents/ARCHITECTURE.md (dependency rules + 'where does code go' decision tree), src/backend/base/langflow/main.py (app wiring + lifespan), services/deps.py + services/utils.py (service registry + init order), api/router.py (route map), api/v1/endpoints.py (run path), lfx/services/manager.py (plugin discovery).
- Commands: `make init` (uv sync --frozen --extra postgresql + npm install + pre-commit), `make backend`/`make frontend` for dev servers, `LFX_DEV=1 make backend` for dynamic component loading, `make unit_tests` / `uv run pytest path::test`, `make lint` (mypy), `uv run git commit` (pre-commit needs uv's env).
- Sub-package test trap: `uv sync` only resolves the top-level workspace; tests inside langflow-base or lfx need `uv sync --group dev --package langflow-base` (etc.) first or dev-only deps like fakeredis are missing and tests fail oddly.
- Never rename a Component class — the class name is the persisted identifier matching components in saved flows and flagging UI updates; renaming breaks existing flows. New components go in src/bundles/<provider>/ (with extension.json) or lfx/components, inheriting lfx Component.
- Dependency direction is enforced by convention: lfx MUST NOT import langflow.* (langflow-base installs the `langflow` package); langflow-base must not import vendor component modules; shared primitives go in lfx/base, never langflow/base; lfx-* bundles must not import application services.
- DB changes: models live in services/database/models/*; every change needs `make alembic-revision` + `make alembic-upgrade`; alembic/versions is excluded from ruff; note async-session traps — cascade_delete_flow lists updatable columns explicitly because getattr on unloaded relationships raises MissingGreenlet.
- Async discipline: blockbuster flags blocking calls in tests; use session_scope() (get_session raises NotImplementedError); DB tests can fail in batch but pass individually; set LANGFLOW_MODELS_DEV_REFRESH=false to keep the network-touching refresh task out of tests.
- Security invariants to preserve when editing run paths: owner-only tweaks (_enforce_owner_only_tweaks), deny->404 UUID-privacy reframes (deny_to_404_unless_readable), public-flow file namespace regex, session namespacing, and prepare_flow_build_for_user before any stored graph executes — these encode prior CVEs.
- Lint conventions: ruff select=ALL at line-length 120 with google docstrings; intentional violations are codified per-file (exec in lfx/custom/validate.py, pickle in services/cache, SLF001 for MCP/third-party internals) — match the pattern or add a justified per-file-ignore; frontend uses biome + tsc --noEmit.
- Version bumps: `make patch v=X.Y.Z` updates pyproject.toml, src/backend/base/pyproject.toml, src/frontend/package.json together; bundle lfx pins are re-synced by scripts/ci/sync_bundle_lfx_pin.py — don't hand-edit versions in only one manifest.
- Hotspot map: main.py (1277 lines) encodes fragile startup ordering documented by inline issue references; __main__.py's ensure_multi_worker_safe guards a real race; endpoints.py's auth/widening wrappers exist because raw Depends helpers once bypassed user scoping — preserve the wrapper pattern. (inferred)
Languages
| Language | Files | Share of source |
|---|---|---|
| Python | 4077 | 53% |
| TypeScript | 2523 | 33% |
| Markdown | 1514 | docs/config |
| JavaScript | 656 | 8% |
| Shell | 449 | 6% |
| JSON | 389 | docs/config |
| YAML | 83 | docs/config |
| TOML | 29 | 0% |
Start reading here
Probable source entrypoint: src/ — inferred from the directory tree and manifest, not the upstream docs.
Source roots
src/ (1858 files)
Repository structure
| Top-level path | Files |
|---|---|
docs/ | 1862 |
src/ | 1858 |
scripts/ | 103 |
.github/ | 63 |
.agents/ | 37 |
docker/ | 17 |
deploy/ | 13 |
regressions/ | 5 |
Docs found: CLAUDE.mdCONTRIBUTING.mdREADME.mddocs/DEPRECATED_MACOS_SUPPORT_INVESTIGATION.mddocs/README.mddocs/TORCH_MACOS_AMD64_PYTHON313_INVESTIGATION.mddocs/agents/ANTI-PATTERNS.mddocs/agents/ARCHITECTURE.mddocs/agents/COMPONENTS.mddocs/agents/CONTRACTS.md
Package
package.json — , 1 declared dependencies.
@types/node
CI & config entrypoints
README.md— Repository readmeCLAUDE.md— Claude instructions.env.example— Required env vars.github/workflows/a11y-scan.yml— GitHub Actions workflow.github/workflows/a11y-unit-tests.yml— GitHub Actions workflow.github/workflows/add-labels.yml— GitHub Actions workflow.github/workflows/ci-scripts-test.yml— GitHub Actions workflow.github/workflows/ci.yml— GitHub Actions workflow.github/workflows/codeql.yml— GitHub Actions workflow.github/workflows/community-label.yml— GitHub Actions workflow
Structural health signals
Average score 7.5/10 across 96 analyzed source files; 14 hotspots surfaced.
scripts/ci/bundle_release_plan.py— 1119 lines, score 4.7 (Large file, Deep nesting)scripts/migrate/port_bundle.py— 1233 lines, score 5.4 (Large file, Deep nesting)scripts/a11y/a11y_scan.py— 1166 lines, score 5.4 (Large file, Deep nesting)
These are deterministic review leads — size, churn, and structural signals — not proof of a bug in this project.
Recent history
5621dcfd84e12026-09-15 — chore: merge release-1.12.2 into mainc979695ce6602026-09-15 — fix(jobs): atomically claim expired HITL pauses (#15096)5cb970863e6c2026-09-15 — fix(lfx): close temp file before deleting failed cloud downloads (#15074)999fc3583d682026-09-14 — fix: warn when restricted workflows substitute saved component code (#15090)2029e8f772d42026-09-14 — fix: allow concurrent flow version attachments on PostgreSQL (#15086)