# Bundle API Stable surface that Langflow Extension Bundles consume. Every public symbol listed below is part of the contract: changes to its name, signature, semantics, or visibility require a coordinated version bump and a `## Changelog` entry. This document is paired with the integer **`BUNDLE_API_VERSION`** declared in [`lfx.extension.manifest`](src/lfx/src/lfx/extension/manifest.py). Manifests declare the contract versions they support via `lfx.compat: ["1"]`; a bundle that does not list `str(BUNDLE_API_VERSION)` is rejected at install time with `version-constraint-unsatisfied`. > **CI gate:** any PR that modifies a file containing an in-scope surface MUST > add a `## Changelog` entry describing the change. The CI guard > [`scripts/migrate/check_bundle_api_changelog.py`](scripts/migrate/check_bundle_api_changelog.py) > enforces this. Pure-internal refactors that preserve every public symbol's > name and signature do not require a changelog entry, but reviewers should be > skeptical. --- ## Surface (v0) ### Component base class | Symbol | Source | | --- | --- | | `Component` | `lfx.custom.custom_component.component.Component` | | `Component.build()` (declared on subclasses) | call site of every loaded bundle module | | `Component.inputs` | declarative input list | | `Component.outputs` | declarative output list | | `Component.display_name` / `Component.description` / `Component.icon` / `Component.documentation` | metadata read by the palette | | `Component.name` | optional override of the registry class name | ### Inputs | Symbol | Source | | --- | --- | | `Input` (base) | `lfx.io` | | `MessageTextInput` / `MultilineInput` / `SecretStrInput` | `lfx.io` | | `IntInput` / `FloatInput` / `BoolInput` | `lfx.io` | | `DropdownInput` / `TabInput` | `lfx.io` | | `DictInput` / `NestedDictInput` | `lfx.io` | | `FileInput` / `LinkInput` | `lfx.io` | | `HandleInput` | `lfx.io` | ### Outputs | Symbol | Source | | --- | --- | | `Output` | `lfx.io` | ### Schema types | Symbol | Source | | --- | --- | | `Data` | `lfx.schema.data` | | `DataFrame` | `lfx.schema.dataframe` | | `Message` | `lfx.schema.message` | ### Manifest contract (consumed by the loader) | Symbol | Source | | --- | --- | | Manifest schema (`extension.json` / `[tool.langflow.extension]`) | `lfx.extension.manifest.ExtensionManifest` | | `BundleRef` (one entry in `bundles[]`) | `lfx.extension.manifest.BundleRef` | | `LfxCompat` (declared as `manifest.lfx`) | `lfx.extension.manifest.LfxCompat` | | `BUNDLE_API_VERSION` (the integer this lfx ships) | `lfx.extension.manifest` | | `EXTENSION_SCHEMA_URL` / `SCHEMA_VERSION` | `lfx.extension.manifest` | Slot vocabulary: `official` (installed pip distributions and seed directories) and `extra` (paths declared in `LANGFLOW_COMPONENTS_PATH`). Component IDs at runtime are `ext::@`. ### Discovery + loading entry points | Symbol | Source | | --- | --- | | `load_extension(root)` | `lfx.extension.loader` | | `load_installed_extensions()` | `lfx.extension.loader` | | `discover_inline_bundles()` | `lfx.extension.loader` | | `discover_installed_extensions()` / `discover_seed_extensions()` / `discover_all_extensions()` | `lfx.extension.discovery` | | `LoadedComponent` | `lfx.extension.loader` (frozen dataclass; what the registry stores) | | `LoadResult` | `lfx.extension.loader` | | `SLOT_OFFICIAL` / `SLOT_EXTRA` | `lfx.extension.loader` | ### Reload pipeline | Symbol | Source | | --- | --- | | `reload_bundle(registry, bundle_name)` | `lfx.extension.reload` | | `BundleRegistry` | `lfx.extension.bundle_registry` | | `BundleRecord` | `lfx.extension.bundle_registry` | | `ReloadInProgressError` | `lfx.extension.bundle_registry` | | `POST /api/v1/extensions/{id}/bundles/{name}/reload` | `langflow.api.v1.extensions` | ### Errors | Symbol | Source | | --- | --- | | `ExtensionError` | `lfx.extension.errors` | | `ExtensionErrorCollection` | `lfx.extension.errors` | | `format_extension_error(error)` | `lfx.extension.errors` | | `ERROR_CODES` (frozenset of every typed code) | `lfx.extension.errors` | The full kebab-case discriminant set is the contract — adding a code is backward-compatible; removing or renaming a code is a breaking change and requires a `BUNDLE_API_VERSION` bump. ### Validate / authoring CLI | Symbol | Source | | --- | --- | | `validate_extension(root, *, execute_imports=False)` | `lfx.extension.validate` | | `ValidateReport` | `lfx.extension.validate` | | `lfx extension validate` (CLI) | `lfx.cli._extension_commands` | | `lfx extension schema` (CLI) | `lfx.cli._extension_commands` | | `lfx extension init` (CLI) | `lfx.cli._extension_commands` | | `lfx extension dev register` / `unregister` / `list` (CLI) | `lfx.cli._extension_commands` | | `lfx extension list` (CLI) | `lfx.cli._extension_commands` | | `lfx extension reload` (CLI) | `lfx.cli._extension_commands` | ### Migration | Symbol | Source | | --- | --- | | Migration-table file | `src/lfx/src/lfx/extension/migration/migration_table.json` | | `MigrationEntry` | `lfx.extension.migration.schema` | | `MigrationTable` | `lfx.extension.migration.schema` | | `migrate_flow_payload(payload, table)` | `lfx.extension.migration.rewrite` | | `MIGRATION_SCHEMA_VERSION` | `lfx.extension.migration.schema` | --- ## Out of scope (v0) These are reserved in the manifest schema and produce a typed `field-deferred-in-this-milestone` error if set; they are NOT part of the v0 contract: - `services` — bundle-declared service factories - `routes` — bundle-mounted HTTP routes - `hooks` — bundle-declared lifecycle hooks - `starter_projects` — bundle-shipped starter flows - `userConfig` — bundle-declared user-config schema - Multi-bundle manifests (`bundles` list with length > 1) --- ## Pilot recommendation For LE-1023 (B1 pilot migration), the recommended target is **`duckduckgo`**. Rationale: - Single component (`DuckDuckGoSearchComponent`) in a single file (`duck_duck_go_search_run.py`). - Zero git churn over the last six months. - Modern `Component` base class (no `LCToolComponent` legacy). - No authentication required — failure mode is a single failed request, not a paid-API outage. - Class name is globally unique across `src/lfx/src/lfx/components/**`, so the bare-name migration entry is allowed by `check_bare_names.py`. This is a recommendation, not a decision — the engineer who picks up B1 owns the call. --- ## Changelog ### v0 (this release) - Initial surface enumerated above. Frozen as `BUNDLE_API_VERSION = 1`.