Catalog and model policy
On a shared Langflow server, anyone who can build a flow can use every component and model.
Superusers can create catalog and model policy to hide components, templates, model providers, and models from the Langflow visual builder.
OSS Langflow has no visual editor for policy management. Instead, use the APIs on this page with a superuser API key or session. This is the same credential the Users API requires.
An empty policy is the default, and has no restrictions. Nothing is restricted until a superuser writes a policy.
The six lists of restrictions are saved in one policy bundle.
Replace the policy bundle with a PUT request.
Send every list you want in effect with the PUT request.
Send [] for a list you want unrestricted.
| You want to… | Field | Example |
|---|---|---|
| Hide a component and refuse flows that use it | blocked_component_keys | PythonREPLComponent (Python Interpreter) |
| Hide a starter template | blocked_template_keys | basic_prompting |
| Allow only some providers | approved_provider_ids | ["openai", "anthropic"]. [] means every provider. |
| Block a model | blocked_model_keys | gpt-4o, openai::gpt-4o, or openai::llm::gpt-4o |
| Allow only some integrations | approved_integration_provider_ids | ["google", "slack"]. [] means every integration. |
| Block one integration action | blocked_integration_action_keys | integrations.google.drive.delete |
A blocked component disappears from the palette. Saving or running a flow that still contains the component fails.
A blocked template is omitted from the starter list.
An integration outside approved_integration_provider_ids, or whose every action is
in blocked_integration_action_keys, disappears from the palette and from
GET /api/v1/integrations. Running a saved flow that still uses it fails before the
provider is contacted, and creating or authorizing a connection for it returns 403.
Catalog policy hides or refuses named components, including built-ins.
To block creating and editing custom code, set LANGFLOW_ALLOW_CUSTOM_COMPONENTS.
To block built-in code-execution components at runtime without a policy bundle, set LANGFLOW_BLOCK_CODE_INTERPRETER_COMPONENTS.
Apply a policy
Apply any database migrations before sending requests to these endpoints.
An uninitialized bundle returns 503.
-
Create a superuser API key.
-
Query how many flows a component would affect before you block it.
GET /api/v1/catalog-policy/usagereturns the number of affected flows.GET /api/v1/catalog-policy/usage/flows?component=PythonREPLComponentlists the affected flows.For field names and key formats, see How to name what you block.
-
Read the current policy bundle, and copy
revision.- Python
- JavaScript
- curl
import osimport requestsurl = f"{os.environ['LANGFLOW_URL']}/api/v1/policy-bundle"headers = {"accept": "application/json","x-api-key": os.environ["LANGFLOW_API_KEY"],}response = requests.get(url, headers=headers)response.raise_for_status()print(response.json())const url = `${process.env.LANGFLOW_URL}/api/v1/policy-bundle`;const response = await fetch(url, {method: "GET",headers: {accept: "application/json","x-api-key": process.env.LANGFLOW_API_KEY,},});if (!response.ok) {throw new Error(`HTTP ${response.status}`);}console.log(await response.json());curl -X GET \"$LANGFLOW_URL/api/v1/policy-bundle" \-H "accept: application/json" \-H "x-api-key: $LANGFLOW_API_KEY"Result
{"revision": 1,"initialized": true,"source": "default","approved_provider_ids": [],"blocked_component_keys": [],"blocked_template_keys": [],"blocked_model_keys": [],"content_hash": "ef74cccb21593ac02fc514e669c911768e740b1135d7a91d3fb15f72837ab762","created_at": null,"created_by": null,"reason": null,"rollback_of_revision": null,"managed_externally": false}tipIf
managed_externallyistrue, an external controller owns policy. Reads still work. Writes return409. -
Replace the bundle with a
PUTrequest that includes every list you want in effect. Send therevisionnumber copied from step 3 asexpected_revision. Send[]for a list you want unrestricted.- Python
- JavaScript
- curl
import osimport requestsbase = os.environ["LANGFLOW_URL"]headers = {"accept": "application/json","Content-Type": "application/json","x-api-key": os.environ["LANGFLOW_API_KEY"],}current = requests.get(f"{base}/api/v1/policy-bundle", headers=headers)current.raise_for_status()revision = current.json()["revision"]payload = {"expected_revision": revision,"approved_provider_ids": ["openai", "anthropic"],"blocked_component_keys": ["PythonREPLComponent"],"blocked_template_keys": [],"blocked_model_keys": ["openai::llm::gpt-4o"],"reason": "Limit providers and block code execution",}response = requests.put(f"{base}/api/v1/policy-bundle", headers=headers, json=payload)response.raise_for_status()print(response.json())const base = process.env.LANGFLOW_URL;const headers = {accept: "application/json","Content-Type": "application/json","x-api-key": process.env.LANGFLOW_API_KEY,};const current = await fetch(`${base}/api/v1/policy-bundle`, { headers });if (!current.ok) {throw new Error(`HTTP ${current.status}`);}const revision = (await current.json()).revision;const response = await fetch(`${base}/api/v1/policy-bundle`, {method: "PUT",headers,body: JSON.stringify({expected_revision: revision,approved_provider_ids: ["openai", "anthropic"],blocked_component_keys: ["PythonREPLComponent"],blocked_template_keys: [],blocked_model_keys: ["openai::llm::gpt-4o"],reason: "Limit providers and block code execution",}),});if (!response.ok) {throw new Error(`HTTP ${response.status}`);}console.log(await response.json());curl -X PUT \"$LANGFLOW_URL/api/v1/policy-bundle" \-H "accept: application/json" \-H "Content-Type: application/json" \-H "x-api-key: $LANGFLOW_API_KEY" \-d '{"expected_revision": 1,"approved_provider_ids": ["openai", "anthropic"],"blocked_component_keys": ["PythonREPLComponent"],"blocked_template_keys": [],"blocked_model_keys": ["openai::llm::gpt-4o"],"reason": "Limit providers and block code execution"}'If another user updated the policy first, the
PUTreturns409. Repeat from step 3 with an updatedrevisionnumber.{"detail": {"message": "Policy bundle revision conflict","expected_revision": 1,"active_revision": 2}} -
Optional: Undo a prior revision.
GET /api/v1/policy-bundle/historylists prior bundle policy revisions.To restore a revision,
POSTto/api/v1/policy-bundle/rollback/{revision}with the currentexpected_revision:curl -X POST \"$LANGFLOW_URL/api/v1/policy-bundle/rollback/1" \-H "accept: application/json" \-H "Content-Type: application/json" \-H "x-api-key: $LANGFLOW_API_KEY" \-d '{"expected_revision": 2, "reason": "Revert last change"}'
How to name blocked items
Components use the type stored in the flow JSON, such as PythonREPLComponent or ChatInput.
That is the component's class identity, not the label in the visual builder.
Keys are case-sensitive.
Older aliases still match, so blocking PythonREPLComponent also catches nodes saved under a previous type for the same component.
"blocked_component_keys": ["PythonREPLComponent"]
Starter templates use a lowercase slug of the English name with underscores, such as basic_prompting.
"blocked_template_keys": ["basic_prompting"]
Providers use lowercase IDs, such as openai.
GET /api/v1/model-provider-policy lists every provider the process is aware of, with provider_id and display name.
"approved_provider_ids": ["openai", "anthropic"]
Models use the identities in the model picker.
"blocked_model_keys": ["gpt-4o", "openai::gpt-4o", "openai::llm::gpt-4o"]
Integration providers use their manifest provider_id, in lowercase.
"approved_integration_provider_ids": ["google", "slack"]
Integration actions use the policy_keys a bundle declares for each capability.
A key is always integrations.<provider_id>.<action>; missing provider or action
segments and other malformed syntax are rejected with 422 before anything is written.
Keys are trimmed and case-folded. A syntactically valid key for a provider that
is not installed is accepted, so operators can configure policy before installing
a bundle. This does not verify that the provider or action exists; use the loaded
catalog to check the keys when the bundle is installed.
GET /api/v1/integrations?include_blocked=true lists every loaded provider with the
policy_keys of each of its actions, so you can copy the exact key to block.
"blocked_integration_action_keys": ["integrations.google.drive.delete"]
Blocking one key of an action blocks the whole action.
A component stays visible while at least one of its actions is still allowed.
At execution, an action-picker bundle declares the selected capabilities through
Component.select_integration_capabilities. Blocking delete therefore does not
block an invocation selecting search. Bundles that do not implement selection
must be allowed to use every capability they declare.
Custom toolsets that override _get_tools() or to_toolkit() must also be allowed
to use every declared capability before construction, because their tools do not
pass through the standard per-invocation action checks. Their connection leases
cover that full set even if they implement the selection hook.
An empty or undeclared action selection returns HTTP 422 with
error_code: "action-unsupported"; it is not a governance denial.
Blocked executions return HTTP 403 with error_code: "policy-blocked" and an
administrator-action hint. Session-backed graph builds record the denial as an error
message in flow history.
Change one list at a time
Use these endpoints when you want to change only components, templates, or providers.
If you are changing more than one list, use one PUT to /api/v1/policy-bundle instead. See Apply a policy.
-
To hide or unhide components,
PUTthe complete blocked list to/api/v1/catalog-policy/components. APUTreplaces the whole list. Send{"blocked": []}to unblock every component.curl -X PUT \"$LANGFLOW_URL/api/v1/catalog-policy/components" \-H "accept: application/json" \-H "Content-Type: application/json" \-H "x-api-key: $LANGFLOW_API_KEY" \-d '{"blocked": ["PythonREPLComponent"]}'To read the current list first,
GET /api/v1/catalog-policy/components. -
To hide or unhide starter templates,
PUTthe complete blocked list to/api/v1/catalog-policy/templates.curl -X PUT \"$LANGFLOW_URL/api/v1/catalog-policy/templates" \-H "accept: application/json" \-H "Content-Type: application/json" \-H "x-api-key: $LANGFLOW_API_KEY" \-d '{"blocked": ["basic_prompting"]}'To read the current list first,
GET /api/v1/catalog-policy/templates. -
Optional: List the flows that include a component.
GET /api/v1/catalog-policy/usagereturns the number of flows.GET /api/v1/catalog-policy/usage/flows?component=PythonREPLComponentlists the affected flows. -
To allow only some providers,
PUTthe complete approved list to/api/v1/model-provider-policy. Send{"approved_provider_ids": []}to allow every provider.curl -X PUT \"$LANGFLOW_URL/api/v1/model-provider-policy" \-H "accept: application/json" \-H "Content-Type: application/json" \-H "x-api-key: $LANGFLOW_API_KEY" \-d '{"approved_provider_ids": ["openai", "anthropic"]}'Set
blocked_model_keyswithPUT /api/v1/policy-bundle. -
To see the integration policy that applies to you,
GET /api/v1/integrations/policy/effective.It returns the caller's allowed providers and whether a plugin owns the policy instead of the bundle. Only superusers receive the operator's blocked action keys and the complete loaded provider list. Other users receive an empty
blocked_action_keyslist and only allowed providers inloaded_provider_ids; useGET /api/v1/integrationsto list usable actions. Checkunrestrictedbefore interpreting an empty approved provider list: an externally managed empty ceiling can deny every provider. Setapproved_integration_provider_idsandblocked_integration_action_keyswithPUT /api/v1/policy-bundle.
An empty persisted approved_integration_provider_ids list means unrestricted.
Installing a plugin must preserve that meaning. Denying all providers requires
an explicit external policy configuration; a plugin changing existing policy
semantics must provide an operator-visible migration or configuration step before
enforcement changes. This API does not perform that migration automatically.
Before enabling external enforcement, verify the intended provider set and check
managed_externally and unrestricted on the effective-policy endpoint.
See also
Was this page helpful?