Skip to content

Cascading Dropdowns

Create dropdowns that update based on other field selections

Cascading dropdowns let users select from filtered options based on previous selections. For example: Department → Manager, or Country → State → City.

A form where selecting a department dynamically populates a managers dropdown with only managers from that department.

  • Form with linked workflow
  • Basic understanding of data providers

Step 1: Create department provider (workspace/data_providers/departments.py):

from bifrost import data_provider, ExecutionContext
@data_provider(
name="get_departments",
description="List all departments",
cache_ttl_seconds=3600 # Cache 1 hour
)
async def get_departments(context: ExecutionContext):
return [
{"label": "Engineering", "value": "eng"},
{"label": "Sales", "value": "sales"},
{"label": "Support", "value": "support"}
]

Step 2: Create managers provider with department parameter:

from bifrost import data_provider, param, ExecutionContext
@data_provider(name="get_managers_by_dept")
@param("department", type="string", required=True)
async def get_managers_by_dept(context: ExecutionContext, department: str):
# Fetch managers filtered by department
managers = await db.query(
"SELECT id, name FROM users WHERE department = ? AND is_manager = true",
department
)
return [
{"label": mgr["name"], "value": mgr["id"]}
for mgr in managers
]

Add both parameters to your workflow:

from bifrost import workflow, param, ExecutionContext
@workflow(name="assign_to_manager")
@param("department", type="string", data_provider="get_departments")
@param("manager", type="string", data_provider="get_managers_by_dept")
async def assign_to_manager(
context: ExecutionContext,
department: str,
manager: str
):
# Assign work to selected manager
return {
"department": department,
"manager_id": manager,
"assigned": True
}
  1. In form builder, drag the department field to canvas

    • Auto-configured with get_departments provider
  2. Drag the manager field to canvas

    • Click to edit field
    • Under Data Provider, select get_managers_by_dept
  3. Configure Data Provider Inputs:

    • Click Data Provider Inputs
    • For department parameter:
      • Mode: Field Reference
      • Field Name: department
  1. Save the form
  2. Click Launch
  3. Select a department
  4. Watch the managers dropdown populate with filtered results
  5. Select a manager and submit

Three-Level Cascade (Country → State → City)

Section titled “Three-Level Cascade (Country → State → City)”
@data_provider(name="get_countries")
async def get_countries(context):
return [
{"label": "United States", "value": "us"},
{"label": "Canada", "value": "ca"}
]
@data_provider(name="get_states")
@param("country", type="string", required=True)
async def get_states(context, country: str):
states = await db.query(
"SELECT * FROM states WHERE country_code = ?",
country
)
return [{"label": s.name, "value": s.code} for s in states]
@data_provider(name="get_cities")
@param("state", type="string", required=True)
async def get_cities(context, state: str):
cities = await db.query(
"SELECT * FROM cities WHERE state_code = ?",
state
)
return [{"label": c.name, "value": c.id} for c in cities]

Configure in workflow:

@param("country", type="string", data_provider="get_countries")
@param("state", type="string", data_provider="get_states")
@param("city", type="string", data_provider="get_cities")

In form, configure Data Provider Inputs:

  • state field → country parameter → Field Reference: country
  • city field → state parameter → Field Reference: state

Pass multiple field values to a provider:

@data_provider(name="get_filtered_products")
@param("category", type="string", required=True)
@param("price_range", type="string", required=True)
async def get_filtered_products(context, category: str, price_range: str):
products = await db.query(
"SELECT * FROM products WHERE category = ? AND price_range = ?",
category, price_range
)
return [{"label": p.name, "value": p.id} for p in products]

In form Data Provider Inputs:

  • category parameter → Field Reference: category
  • price_range parameter → Field Reference: price_range

Mix static values with field references:

@data_provider(name="get_regional_managers")
@param("department", type="string", required=True)
@param("region", type="string", required=True)
async def get_regional_managers(context, department: str, region: str):
# region comes from static value, department from form field
return await fetch_managers(department, region)

In form Data Provider Inputs:

  • department parameter → Field Reference: department
  • region parameter → Static: "west_coast"

Use JavaScript expressions for computed values:

In form Data Provider Inputs:

  • Mode: Expression
  • Value: context.field.country + '_' + context.field.state

Filter all levels by organization:

@data_provider(name="get_org_departments")
async def get_org_departments(context):
return await db.query(
"SELECT * FROM departments WHERE org_id = ?",
context.org_id
)
@data_provider(name="get_org_managers")
@param("department", type="string", required=True)
async def get_org_managers(context, department: str):
return await db.query(
"SELECT * FROM users WHERE org_id = ? AND department = ? AND is_manager = true",
context.org_id, department
)

Show helpful message when no options available:

@data_provider(name="get_managers_by_dept")
@param("department", type="string", required=True)
async def get_managers_by_dept(context, department: str):
managers = await db.query(
"SELECT * FROM users WHERE department = ? AND is_manager = true",
department
)
if not managers:
return [{"label": "No managers in this department", "value": "", "disabled": True}]
return [{"label": m.name, "value": m.id} for m in managers]

Dropdown not updating: Verify Data Provider Inputs configuration. Field Name must exactly match the source field name.

Empty dropdown: Check provider returns correct format with label and value. Test provider independently.

Slow updates: Increase cache TTL on parent provider. Use shorter TTL on dependent provider for freshness.

  • Cache parent providers: Long TTL on rarely-changing data (departments, countries)
  • Short cache on dependent providers: Balance freshness with performance
  • Limit results: Return max ~100 options per dropdown
  • Show loading states: Dependent providers may take time to fetch
  • Test edge cases: What if parent selection is cleared?